Wiem, że nie ma do tego standardowej funkcji w C. Zastanawiałem się, jakie są techniki, aby to zrobić w systemie Windows i * nix? (Windows XP jest moim najważniejszym systemem operacyjnym, w którym teraz mogę to robić).
Wiem, że nie ma do tego standardowej funkcji w C. Zastanawiałem się, jakie są techniki, aby to zrobić w systemie Windows i * nix? (Windows XP jest moim najważniejszym systemem operacyjnym, w którym teraz mogę to robić).
Odpowiedzi:
glibc udostępnia funkcję backtrace ().
http://www.gnu.org/software/libc/manual/html_node/Backtraces.html
char ** backtrace_symbols (void *const *buffer, int size)
void backtrace_symbols_fd(void *const *buffer, int size, int fd)
który może wysłać dane wyjściowe bezpośrednio do stdout / err.
backtrace_symbols()
jest do bani. Wymaga eksportowania wszystkich symboli i nie obsługuje symboli DWARF (debugowania). libbacktrace jest znacznie lepszą opcją w wielu (większości) przypadkach.
Istnieje backtrace () i backtrace_symbols ():
Ze strony podręcznika:
#include <execinfo.h>
#include <stdio.h>
...
void* callstack[128];
int i, frames = backtrace(callstack, 128);
char** strs = backtrace_symbols(callstack, frames);
for (i = 0; i < frames; ++i) {
printf("%s\n", strs[i]);
}
free(strs);
...
Jednym ze sposobów użycia tego w wygodniejszy sposób / OOP jest zapisanie wyniku operacji backtrace_symbols () w konstruktorze klasy wyjątków. W związku z tym za każdym razem, gdy rzucasz wyjątek tego typu, masz ślad stosu. Następnie wystarczy udostępnić funkcję drukowania. Na przykład:
class MyException : public std::exception {
char ** strs;
MyException( const std::string & message ) {
int i, frames = backtrace(callstack, 128);
strs = backtrace_symbols(callstack, frames);
}
void printStackTrace() {
for (i = 0; i < frames; ++i) {
printf("%s\n", strs[i]);
}
free(strs);
}
};
...
try {
throw MyException("Oops!");
} catch ( MyException e ) {
e.printStackTrace();
}
Ta da!
Uwaga: włączenie flag optymalizacji może spowodować, że wynikowy ślad stosu będzie niedokładny. W idealnym przypadku można by użyć tej możliwości z włączonymi flagami debugowania i wyłączonymi flagami optymalizacji.
W przypadku systemu Windows sprawdź interfejs API StackWalk64 () (również w 32-bitowym systemie Windows). W przypadku UNIX powinieneś użyć natywnego sposobu systemu operacyjnego, aby to zrobić, lub wrócić do backtrace () biblioteki glibc, jeśli jest dostępna.
Pamiętaj jednak, że zrobienie Stacktrace w kodzie natywnym rzadko jest dobrym pomysłem - nie dlatego, że nie jest to możliwe, ale dlatego, że zwykle próbujesz osiągnąć coś złego.
W większości przypadków ludzie próbują uzyskać ślad stosu, powiedzmy, w wyjątkowych okolicznościach, takich jak przechwycenie wyjątku, niepowodzenie asercji lub - co najgorsze i najgorsze ze wszystkich - kiedy otrzymujesz fatalny „wyjątek” lub sygnał podobny do naruszenie segmentacji.
Biorąc pod uwagę ten ostatni problem, większość interfejsów API będzie wymagać jawnego przydzielenia pamięci lub może to zrobić wewnętrznie. Robienie tego w kruchym stanie, w jakim obecnie znajduje się program, może poważnie pogorszyć sytuację. Na przykład raport o awarii (lub coredump) nie będzie odzwierciedlał faktycznej przyczyny problemu, ale nieudana próba jego rozwiązania).
Zakładam, że próbujesz osiągnąć tę krytyczną obsługę błędów, ponieważ większość ludzi wydaje się tego próbować, jeśli chodzi o uzyskanie śledzenia stosu. Jeśli tak, oparłbym się na debugerze (podczas rozwoju) i pozwoliłbym procesowi coredumpować w produkcji (lub mini-dump w Windows). Razem z odpowiednim zarządzaniem symbolami, nie powinieneś mieć problemów z ustaleniem przyczyny instrukcji po śmierci.
Powinieneś używać rozwijanej biblioteki .
unw_cursor_t cursor; unw_context_t uc;
unw_word_t ip, sp;
unw_getcontext(&uc);
unw_init_local(&cursor, &uc);
unsigned long a[100];
int ctr = 0;
while (unw_step(&cursor) > 0) {
unw_get_reg(&cursor, UNW_REG_IP, &ip);
unw_get_reg(&cursor, UNW_REG_SP, &sp);
if (ctr >= 10) break;
a[ctr++] = ip;
}
Twoje podejście również działałoby dobrze, chyba że wykonasz połączenie z udostępnionej biblioteki.
Możesz użyć addr2line
polecenia w systemie Linux, aby uzyskać numer funkcji źródłowej / numer linii odpowiedniego komputera.
Nie ma niezależnego od platformy sposobu, aby to zrobić.
Najbliższą rzeczą, jaką możesz zrobić, jest uruchomienie kodu bez optymalizacji. W ten sposób możesz dołączyć do procesu (używając wizualnego debuggera C ++ lub GDB) i uzyskać użyteczny ślad stosu.
W przypadku systemu Windows CaptureStackBackTrace()
jest to również opcja, która wymaga mniej przygotowanego kodu po stronie użytkownika niż StackWalk64()
to. (Poza tym w przypadku podobnego scenariusza, który miałem, CaptureStackBackTrace()
działało lepiej (bardziej niezawodnie) niż StackWalk64()
.)
Solaris ma polecenie pstack , które również zostało skopiowane do Linuksa.
Możesz to zrobić, cofając stos. W rzeczywistości jednak często łatwiej jest dodać identyfikator do stosu wywołań na początku każdej funkcji i wstawić go na końcu, a następnie po prostu przejść przez drukowanie zawartości. To trochę PITA, ale działa dobrze i ostatecznie pozwoli Ci zaoszczędzić czas.
Od kilku lat korzystam z libbacktrace Iana Lance'a Taylora. Jest znacznie czystszy niż funkcje w bibliotece GNU C, które wymagają wyeksportowania wszystkich symboli. Zapewnia więcej narzędzi do generowania śladów wstecznych niż libunwind. I wreszcie, nie jest pokonany przez ASLR, podobnie jak podejścia wymagające narzędzi zewnętrznych, takich jak addr2line
.
Libbacktrace był początkowo częścią dystrybucji GCC, ale teraz jest udostępniany przez autora jako samodzielna biblioteka na licencji BSD:
https://github.com/ianlancetaylor/libbacktrace
W chwili pisania tego tekstu nie używałbym niczego innego, chyba że potrzebowałbym generowania śladów śledzenia na platformie, która nie jest obsługiwana przez libbacktrace.