網站首頁 編程語言 正文
1.介紹打印堆棧信息函數
頭文件:
#include <execinfo.h>
函數原型
int backtrace (void **buffer, int size);
char **backtrace_symbols (void *const *buffer, int size);
void backtrace_symbols_fd (void *const *buffer, int size, int fd);
函數描述
backtrace()函數:獲取函數調用堆棧幀數據,即回溯函數調用列表。數據將放在buffer中。參數size用來指定buffer中可以保存多少個void*元素(表示相應棧幀的地址,一個返回地址)。如果回溯的函數調用大于size,則size個函數調用地址被返回。為了取得全部的函數調用列表,應保證buffer和size足夠大。backtrace函數返回通過buffer返回的地址個數,這個數目不會超過size。如果這個返回值小于size,那么所有的函數調用列表都被保存;如果等于size,那么函數調用列表可能被截斷,此時,一些最開始的函數調用沒有被返回。
backtrace_symbols()函數,參數buffer是從backtrace()函數獲取的數組指針,size是該數組中的元素個數(backtrace()函數的返回值)。該函數主要功能:將從backtrace()函數獲取的地址轉為描述這些地址的字符串數組。每個地址的字符串信息包含對應函數的名字、在函數內的十六進制偏移地址、以及實際的返回地址(十六進制)。需注意的是,當前,只有使用elf二進制格式的程序才能獲取函數名稱和偏移地址,此外,為支持函數名功能,可能需要添加相應的編譯鏈接選項如-rdynamic;否則,只有十六進制的返回地址能被獲取。backtrace_symbols()函數返回值是一個字符串指針,是通過malloc函數申請的空間,使用完后,調用者必需把它釋放掉。注:如果不能為字符串獲取足夠的空間,該函數的返回值為NULL。成功時,backtrace_symbols()函數返回一個由malloc分配的數組;失敗時,返回NULL。
backtrace_symbols_fd()函數,與backtrace_symbols()函數具有相同的功能,不同的是它不會給調用者返回字符串數組,而是將結果寫入文件描述符為fd的文件中,每個函數對應一行。它不會調用malloc函數,因此,它可以應用在函數調用可能失敗的情況下。
2.示例
首先來一個C語言調用上述函數的例子:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <execinfo.h>
#define MAX_SIZE 1024
void print_trace(void)
{
size_t i, size;
void *array[MAX_SIZE];
size = backtrace(array, MAX_SIZE);
char **strings = backtrace_symbols(array, size);
for (i = 0; i < size; i++)
printf("%d# %s\n",i, strings[i]);
free(strings);
}
void my_func_3(void)
{
print_trace();
}
void my_func_1(void)
{
my_func_3();
}
int main(void)
{
my_func_1();
return 0;
}
編譯:
gcc -g main.c -o out
執行結果:
0# ./out(+0x1210) [0x559e1547d210]
1# ./out(+0x12c9) [0x559e1547d2c9]
2# ./out(+0x12d9) [0x559e1547d2d9]
3# ./out(+0x12e9) [0x559e1547d2e9]
4# /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf3) [0x7fed731d8083]
5# ./out(+0x110e) [0x559e1547d10e]
這種打印存在兩個問題:
問題1:沒有打印出調用函數信息
解決方法:編譯時候增加-rdynamic
gcc -g -rdynamic main.c -o out
結果:
0# ./out(print_trace+0x47) [0x559283455210]
1# ./out(my_func_3+0xd) [0x5592834552c9]
2# ./out(my_func_1+0xd) [0x5592834552d9]
3# ./out(main+0xd) [0x5592834552e9]
4# /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf3) [0x7f3d1665c083]
5# ./out(_start+0x2e) [0x55928345510e]
問題2:我們可以用addr2line命令通過地址查看調用函數信息
addr2line -Cif -e out 0x559283455210
結果:產生??
??
??:0
原因:addr2line輸入的地址并非其所接受的地址,addr2line接受的地址是相對偏移地址。
解決方法:編譯的時候增加-no-pie
gcc -g -no-pie -rdynamic main.c -o out
結果:這里的地址和上面的地址就不同了,用這個地址輸入到addr2line中
0# ./out(print_trace+0x47) [0x4011fd]
1# ./out(my_func_3+0xd) [0x4012b6]
2# ./out(my_func_1+0xd) [0x4012c6]
3# ./out(main+0xd) [0x4012d6]
4# /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf3) [0x7fca00584083]
5# ./out(_start+0x2e) [0x4010fe]
root@ubuntu:~# addr2line -Cif -e out 0x4011fd
print_trace
/root/zby/demo/backtrace_demo/main.c:13
注:(這里有個問題解釋不明白,希望有會的大佬看到可以解釋一下,以供學習)addr2line顯示的函數行數是13,但是從代碼中看到這個函數對應的行數不是13,為什么?
現在來一個c++例子:
#include <iostream>
#include <stdio.h>
#include <cxxabi.h>
#include <execinfo.h>
#include <memory>
using namespace std;
void backtrace()
{
void* addresses[256];
const int n = backtrace(addresses, extent< decltype(addresses) > ::value );
const unique_ptr< char*, decltype(&free) > symbols(backtrace_symbols(addresses, n), &free);
for(int i=0; i<n; ++i) {
char* symbol = symbols.get()[i];
char* end = symbol;
cout << symbol << endl;
}
}
void func2()
{
backtrace();
}
void func1()
{
func2();
}
int main()
{
func1();
}
編譯:
g++ main1.cpp -g -rdynamic -no-pie -o out
結果:
./out(_Z9backtracev+0x33) [0x403410]
./out(_Z5func2v+0xd) [0x403538]
./out(_Z5func1v+0xd) [0x403548]
./out(main+0xd) [0x403558]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf3) [0x7f0500e10083]
./out(_start+0x2e) [0x4031de]
上面的調用堆棧中函數名大致能看出來,但是有些奇怪的字母,這種情況需要做處理,c++中應用demangled來解決這個問題,具體操作見下面代碼:
#include <stdio.h>
#include <cxxabi.h>
#include <execinfo.h>
#include <iostream>
#include <memory>
using namespace std;
static string demangle(const char* symbol)
{
const unique_ptr< char, decltype( &free ) > demangled(abi::__cxa_demangle( symbol, 0, 0, 0 ), &free );
if (demangled) {
return demangled.get();
}
else {
return symbol;
}
}
void backtrace()
{
void* addresses[256];
const int n = backtrace(addresses, extent< decltype(addresses) > ::value );
const unique_ptr< char*, decltype(&free) > symbols(backtrace_symbols(addresses, n), &free);
for(int i=0; i<n; ++i) {
char* symbol = symbols.get()[i];
char* end = symbol;
int p = 0;
int q = 0;
while (*end) {
++end;
}
while (end != symbol && *end != '+') {
--end;
}
char* begin = end;
while(begin != symbol && *begin != '(') {
--begin;
}
if (begin != symbol) {
string str1 = string(symbol, ++begin - symbol);
*end++ = '\0';
string result = str1 + demangle(begin) + '+' + end;
cout << "result ==== " << result << endl;
}
else {
cout << symbol << endl;;
}
}
}
void func2()
{
backtrace();
}
void func1()
{
func2();
}
int main()
{
func1();
}
相比于上面的代碼增加了函數名處理,結果:
./out(backtrace()+0x33) [0x4034f0]
./out(func2()+0xd) [0x4038c4]
./out(func1()+0xd) [0x4038d4]
./out(main+0xd) [0x4038e4]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf3) [0x7f6a0c4e9083]
./out(_start+0x2e) [0x4032be]
注:中間指針操作可以優化。
總結
1.linux平臺下可以利用函數backtrace、backtrace_symbols、backtrace_symbols_fd來獲取當時的函數調用堆棧信息
2.使用上述函數時,需要引用頭文件<execinfo.h>,編譯時最好加上-rdynamic選項和-no-pie選項。
3.處理函數名格式c++中可以用demangled解決。
4.可以通過addr2line命令獲取詳細的函數信息。
碼字不易,如有幫助,點贊收藏,如有錯誤,評論指正,謝謝。
END。
原文鏈接:https://blog.csdn.net/weixin_42736510/article/details/126307156
- 上一篇:局域網中共享文件夾
- 下一篇:Linux新特性之btrfs文件系統
相關推薦
- 2022-10-03 React?Native?中實現倒計時功能_React
- 2022-05-03 shell腳本批量創建用戶的方法小結_linux shell
- 2022-07-11 Python內建類型bytes深入理解_python
- 2022-09-25 uniapp封裝request請求的方法
- 2022-12-08 C#與C++?dll之間傳遞字符串string?wchar_t*?char*?IntPtr問題_C#
- 2023-03-26 C#?cefSharep控件的使用詳情_C#教程
- 2022-06-02 Apache?Pulsar結合Hudi構建Lakehouse方案分析_服務器其它
- 2022-07-26 讓一個有寬高的盒子垂直水平居中
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支