[Linux] [C / C ++] Résumé de la méthode d'acquisition des traces

Résumé de la méthode d'acquisition de backtrace au moment du débogage En utilisant LD_PRELOAD, vous pouvez intégrer ultérieurement une trace arrière à n'importe quel endroit sans reconstruire la cible.

Utiliser gdb

Omis car ce sera l'utilisation de gdb

Utilisez libSegFault.so

Uniquement disponible lorsque SEGV se produit dans le programme.

comment utiliser


$ LD_PRELOAD=/lib/libSegFault.so hoge

# or

$ catchsegv hoge

Utilisez __builtin_return_address ()

Vous pouvez obtenir l'adresse de l'appelant en utilisant __builtin_return_address (). En augmentant le nombre d'arguments, vous pouvez vous référer à l'appelant ci-dessus.

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;
}

Créer et exécuter des résultats


$ 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

Connexes: [Linux] [C / C ++] Comment obtenir la valeur d'adresse de retour d'une fonction et le nom de fonction de l'appelant --Qiita

Lors du référencement d'un cadre de pile qui n'existe pas (SEGV se produit)

Notez qu'avec cette méthode, SEGV se produira si vous accédez à une adresse qui n'existe pas lorsque vous augmentez le nombre et revenez en arrière.

Lors du référencement d'un cadre de pile qui n'existe pas(SEGV se produit)


#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;
}

Créer et exécuter des résultats


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

Utiliser le backtrace de la glibc

Un exemple d'utilisation de backtrace () et backtrace_symbols () de la glibc pour afficher une trace arrière.

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;
}

Créer et exécuter des résultats


$ 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]

Utilisez libunwind

Vous pouvez également obtenir une trace en utilisant libunwind. (Libunwind est une bibliothèque qui contrôle les chaînes d'appels, voir "BINARY HACKS # 73") Il y avait un cas où backtrace () ne fonctionnait pas bien sur la carte ARM, donc je l'ai implémenté ici aussi.

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;
}

Créer et exécuter des résultats


$ 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]

Livres

Binary Hacks a beaucoup d'explications pour les exemples ci-dessus.

référence

backtrace() The GNU C Library: Backtraces Return Address - Using the GNU Compiler Collection (GCC) [Demain est toujours frais et sans erreur. @ Memorandum-Backtrace en langage C (trace de pile)] (http://www35.atwiki.jp/futoyama/pages/101.html) Afficher la trace en 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 ++] Résumé de la méthode d'acquisition des traces
Résumé Linux
Résumé des commandes Linux
[Linux] Résumé des commandes de base
Résumé de la méthode d'essai
[Linux] [C / C ++] Résumé de la façon d'obtenir pid, ppid, tid
Résumé de la méthode du noyau de Kaggle [Image]
résumé de la requête d'acquisition de données python-fitbit
Linux une touche de raccourci récapitulative
Résumé des types de distribution Linux
Résumé de l'API d'événement Linux FD
Résumé de la méthode d'implémentation Unity IAP
[Linux] [Réglage initial] [Flutter] Résumé
Un bref résumé de Linux
[Linux] Résumé de la commande utilisateur / groupe