[Linux] [C / C ++] backtrace acquisition method summary

Summary of backtrace acquisition method at the time of debugging If you use LD_PRELOAD, you can embed the backtrace in any place later without rebuilding the target.

use gdb

Omitted because it will be the usage of gdb

Use libSegFault.so

Only available when SEGV occurs in the program.

how to use


$ LD_PRELOAD=/lib/libSegFault.so hoge

# or

$ catchsegv hoge

Use __builtin_return_address ()

You can get the address of the caller by using __builtin_return_address (). By increasing the number of arguments, you can refer to the caller above.

test.c


#include <stdio.h>
#define  __USE_GNU
#include <dlfcn.h>

void hoge() {
	Dl_info info;
	dladdr(__builtin_return_address(0), &info);
	fprintf(stderr, "%s : __builtin_return_address => %p\n", __func__, __builtin_return_address(0));
	fprintf(stderr, "%s : Dl_info.dli_fname => %s\n", __func__, info.dli_fname);
	fprintf(stderr, "%s : Dl_info.dli_fbase => %p\n", __func__, info.dli_fbase);
	fprintf(stderr, "%s : Dl_info.dli_sname => %s\n", __func__, info.dli_sname);
	fprintf(stderr, "%s : Dl_info.dli_saddr => %p\n", __func__, info.dli_saddr);

	dladdr(__builtin_return_address(1), &info);
	fprintf(stderr, "%s : __builtin_return_address => %p\n", __func__, __builtin_return_address(1));
	fprintf(stderr, "%s : Dl_info.dli_fname => %s\n", __func__, info.dli_fname);
	fprintf(stderr, "%s : Dl_info.dli_fbase => %p\n", __func__, info.dli_fbase);
	fprintf(stderr, "%s : Dl_info.dli_sname => %s\n", __func__, info.dli_sname);
	fprintf(stderr, "%s : Dl_info.dli_saddr => %p\n", __func__, info.dli_saddr);
}

int main(int argc, char const* argv[])
{
	hoge();

	Dl_info info;
	dladdr(__builtin_return_address(0), &info);
	fprintf(stderr, "%s : __builtin_return_address => %p\n", __func__, __builtin_return_address(0));
	fprintf(stderr, "%s : Dl_info.dli_fname => %s\n", __func__, info.dli_fname);
	fprintf(stderr, "%s : Dl_info.dli_fbase => %p\n", __func__, info.dli_fbase);
	fprintf(stderr, "%s : Dl_info.dli_sname => %s\n", __func__, info.dli_sname);
	fprintf(stderr, "%s : Dl_info.dli_saddr => %p\n", __func__, info.dli_saddr);

	return 0;
}

Build and execution results


$ gcc test.c -ldl -rdynamic && ./a.out
hoge : __builtin_return_address => 0x4007d7
hoge : Dl_info.dli_fname => ./a.out
hoge : Dl_info.dli_fbase => 0x400000
hoge : Dl_info.dli_sname => (null)
hoge : Dl_info.dli_saddr => (nil)
hoge : __builtin_return_address => 0x7f8889a6c76d
hoge : Dl_info.dli_fname => /lib/x86_64-linux-gnu/libc.so.6
hoge : Dl_info.dli_fbase => 0x7f8889a4b000
hoge : Dl_info.dli_sname => __libc_start_main
hoge : Dl_info.dli_saddr => 0x7f8889a6c680
main : __builtin_return_address => 0x7f8889a6c76d
main : Dl_info.dli_fname => /lib/x86_64-linux-gnu/libc.so.6
main : Dl_info.dli_fbase => 0x7f8889a4b000
main : Dl_info.dli_sname => __libc_start_main
main : Dl_info.dli_saddr => 0x7f8889a6c680

Related: [Linux] [C / C ++] How to get the return address value of a function and the function name of the caller --Qiita

When referencing a stack frame that does not exist (SEGV occurs)

Note that with this method, if you increase the number and go back, SEGV will occur if you access an address that does not exist.

When referencing a stack frame that does not exist(SEGV occurs)


#include <stdio.h>

int main(int argc, char const* argv[])
{
	printf("%p\n", __builtin_return_address(0));
	printf("%p\n", __builtin_return_address(1));
	return 0;
}

Build and execution results


$ gcc test.c && ./a.out
0x7f6c9450076d
Segmentation fault (core dumped)

Use glibc backtrace

An example of using glibc's backtrace () and backtrace_symbols () to output a backtrace.

test.cpp


// backtrace, backtrace_symbols
#include <execinfo.h>

#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <vector>
#include <string>

using RetVec = std::vector<std::string>;

RetVec my_backtrace() {
	auto trace_size = 10;
	void* trace[trace_size];

	auto size = backtrace(trace, trace_size);

	auto symbols = backtrace_symbols(trace, size);

	RetVec result(symbols, symbols + size);

	free(symbols);

	return result;
}

RetVec hoge2() {
	return my_backtrace();
}

RetVec hoge1() {
	return hoge2();
}

int main(int argc, char const* argv[])
{
	auto vec = hoge1();

	int count = vec.size();
	for (auto v : vec) {
		count--;
		fprintf(stderr, "%s : backtrace [%d] %s\n",
				__func__,
				count,
				v.c_str()
			);
	}

	return 0;
}

Build and execution results


$ g++ -g -std=c++11 -o test test.cpp -rdynamic
$ ./test
main : backtrace [5] ./test(_Z12my_backtracev+0x97) [0x4021ff]
main : backtrace [4] ./test(_Z5hoge2v+0x18) [0x4022ac]
main : backtrace [3] ./test(_Z5hoge1v+0x18) [0x4022ca]
main : backtrace [2] ./test(main+0x1c) [0x4022ec]
main : backtrace [1] /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xed) [0x7fc27153076d]
main : backtrace [0] ./test() [0x402079]

Use libunwind

You can also get a backtrace by using libunwind. (Libunwind is a library that controls call chains, see "BINARY HACKS # 73") There was a case where backtrace () did not work well on the ARM board, so I implemented it here as well.

python


// backtrace, backtrace_symbols
#include <execinfo.h>

#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <vector>
#include <string>

#ifndef __USE_GNU
#define __USE_GNU
#endif
#include <dlfcn.h>

#define UNW_LOCAL_ONLY
#include <libunwind.h>

void my_backtrace() {
	unw_cursor_t cursor;
	unw_context_t context;

	unw_getcontext(&context);
	unw_init_local(&cursor, &context);

	auto count = 0;

	do {
		unw_word_t  offset, pc;
		char        fname[64];

		unw_get_reg(&cursor, UNW_REG_IP, &pc);

		fname[0] = '\0';
		(void) unw_get_proc_name(&cursor, fname, sizeof(fname), &offset);

		Dl_info info;
		dladdr((void*)pc, &info);

		fprintf(stderr, "%s : backtrace [%d] %s(%s+0x%lx) [%p]\n",
				__func__,
				count,
				info.dli_fname,
				fname,
				offset,
				(void*)pc
			   );

		count++;

	} while (unw_step(&cursor) > 0);
}

void hoge2() {
	my_backtrace();
}

void hoge1() {
	hoge2();
}

int main(int argc, char const* argv[])
{
	hoge1();
	return 0;
}

Build and execution results


$ g++ -g -std=c++11 -o test test.cpp -rdynamic -lunwind -ldl
$ ./test
my_backtrace : backtrace [0] ./test(_Z12my_backtracev+0x29) [0x400c81]
my_backtrace : backtrace [1] ./test(_Z5hoge2v+0x9) [0x400d92]
my_backtrace : backtrace [2] ./test(_Z5hoge1v+0x9) [0x400d9d]
my_backtrace : backtrace [3] ./test(main+0x14) [0x400db3]
my_backtrace : backtrace [4] /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xed) [0x7f12a138d76d]
my_backtrace : backtrace [5] ./test(_start+0x29) [0x400b69]

Books

Binary Hacks has a lot of explanations for the above examples.

reference

backtrace() The GNU C Library: Backtraces Return Address - Using the GNU Compiler Collection (GCC) [Tomorrow is always fresh with no mistake in it. @ Memorandum --Back trace (stack trace) in C language] (http://www35.atwiki.jp/futoyama/pages/101.html) View backtrace in C ++-Days of memos (2010-10-15) How to generate a stacktrace when my gcc C++ app crashes - Stack Overflow

libunwind libunwind documentation c - GCC return address of calling function in ARM architecture - Stack Overflow Coding Relic: Pre-mortem Backtracing Backtrace using ptrace in ARM Linux environment-blog of crimsonwoods

Recommended Posts

[Linux] [C / C ++] backtrace acquisition method summary
Linux Summary
Linux Command Summary
[Linux] Basic command summary
OpenVPN Summary + Amazon Linux2
Summary of test method
[Linux] [C / C ++] Summary of how to get pid, ppid, tid
Kaggle Kernel Method Summary [Image]
python-fitbit data acquisition query summary
Linux a summary shortcut key
Summary of Linux distribution types
Linux FD event API summary
Unity IAP implementation method summary
[Linux] [Initial Settings] [Flutter] Summary
A brief summary of Linux
[Linux] User / group command summary