日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學無先后,達者為師

網站首頁 編程語言 正文

記錄:C++打印堆棧信息并優化打印結果

作者:zby-棗菠魚 更新時間: 2022-08-13 編程語言

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

欄目分類
最近更新