Pracuję na systemie Linux z kompilatorem GCC. Gdy mój program C ++ ulega awarii, chciałbym, aby automatycznie generował ślad stosu.
Mój program jest uruchamiany przez wielu różnych użytkowników, a także działa na systemach Linux, Windows i Macintosh (wszystkie wersje są kompilowane przy użyciu gcc
).
Chciałbym, aby mój program był w stanie wygenerować ślad stosu, gdy ulega awarii, a następnym razem, gdy użytkownik go uruchomi, zapyta go, czy można wysłać ślad stosu do mnie, abym mógł wyśledzić problem. Mogę obsłużyć wysyłanie informacji do mnie, ale nie wiem, jak wygenerować ciąg śledzenia. Jakieś pomysły?
Odpowiedzi:
W przypadku systemu Linux i uważam, że w systemie Mac OS X, jeśli używasz gcc lub dowolnego kompilatora korzystającego z glibc, możesz użyć funkcji backtrace () w
execinfo.h
celu wydrukowania pliku stacktrace i wyjścia z wdziękiem, gdy pojawi się błąd segmentacji. Dokumentację można znaleźć w podręczniku libc .Oto przykładowy program, który instaluje
SIGSEGV
moduł obsługi i drukuje ślad stosustderr
podczas segfaulta. Tabaz()
funkcja powoduje segfault, który wyzwala moduł obsługi:Kompilacja z
-g -rdynamic
pozwala uzyskać informacje o symbolu w danych wyjściowych, których glibc może użyć do stworzenia ładnego stacktrace:Wykonanie tego daje ci następujące dane wyjściowe:
Pokazuje moduł ładowania, przesunięcie i funkcję, z której pochodzi każda ramka na stosie. Tutaj można zobaczyć obsługi sygnału na wierzchu stosu i funkcji libc przed
main
opróczmain
,foo
,bar
, ibaz
.źródło
sigaction()
libc. Podczas gdy twój ślad wydaje się być poprawny, czasami stwierdziłem, że konieczne są dodatkowe kroki, aby upewnić się, że faktyczna lokalizacja błędu pojawia się w śladzie wstecznym, ponieważ może zostać zastąpionysigaction()
przez jądro.catchsegv
nie jest tym, czego potrzebuje OP, ale jest świetny do wychwytywania błędów segmentacji i uzyskiwania wszystkich informacji.Jest to nawet łatwiejsze niż „man backtrace”, istnieje mała udokumentowana biblioteka (specyficzna dla GNU) rozpowszechniana z glibc jako libSegFault.so, która, jak sądzę, została napisana przez Ulricha Dreppera do obsługi programu catchsegv (patrz „man catchsegv”).
To daje nam 3 możliwości. Zamiast uruchamiać „program -o hai”:
Uruchom w catchsegv:
Łącze z libSegFault w czasie wykonywania:
Łącze z libSegFault w czasie kompilacji:
We wszystkich 3 przypadkach otrzymasz wyraźniejsze ślady wstecz z mniejszą optymalizacją (gcc -O0 lub -O1) i symbolami debugowania (gcc -g). W przeciwnym razie możesz po prostu skończyć ze stosem adresów pamięci.
Możesz także złapać więcej sygnałów dla śladów stosu za pomocą czegoś takiego:
Dane wyjściowe będą wyglądać mniej więcej tak (zwróć uwagę na ślad u dołu):
Jeśli chcesz poznać szczegóły krwawe, najlepszym źródłem jest niestety źródło: patrz http://sourceware.org/git/?p=glibc.git;a=blob;f=debug/segfault.c i jego katalog nadrzędny http://sourceware.org/git/?p=glibc.git;a=tree;f=debug
źródło
-Wl,--no-as-needed
do flag kompilatora. W przeciwnym razield
rzeczywiście nie będzie łączył się przeciwkolibSegFault
, ponieważ rozpoznaje, że plik binarny nie używa żadnego z jego symboli.Linux
Chociaż użycie funkcji backtrace () w pliku execinfo.h do drukowania stacktrace i wyjścia z gracją po otrzymaniu błędu segmentacji zostało już zasugerowane , nie widzę wzmianki o zawiłościach koniecznych do zapewnienia, że wynikowy ślad wskazuje rzeczywistą lokalizację błąd (przynajmniej dla niektórych architektur - x86 i ARM).
Pierwsze dwa wpisy w łańcuchu ramek stosu po wejściu do modułu obsługi sygnału zawierają adres zwrotny wewnątrz modułu obsługi sygnału i jeden wewnątrz sigaction () w libc. Ramka stosu ostatniej funkcji wywołanej przed sygnałem (będącym lokalizacją błędu) zostaje utracona.
Kod
Wynik
Wszystkie niebezpieczeństwa wywołania funkcji backtrace () w procedurze obsługi sygnałów nadal istnieją i nie należy ich lekceważyć, ale funkcjonalność, którą tu opisałem, jest bardzo pomocna w debugowaniu awarii.
Należy zauważyć, że podany przeze mnie przykład został opracowany / przetestowany w systemie Linux dla x86. Z powodzeniem zaimplementowałem to również w ARM za pomocą
uc_mcontext.arm_pc
zamiastuc_mcontext.eip
.Oto link do artykułu, w którym poznałem szczegóły tej implementacji: http://www.linuxjournal.com/article/6391
źródło
-rdynamic
poleceniem linkerowi dodania wszystkich symboli, nie tylko używanych, do dynamicznej tablicy symboli. Pozwala tobacktrace_symbols()
na konwersję adresów na nazwy funkcjiaddr2line
jakiś sposób użyć polecenia, aby uzyskać dokładną linię, w której nastąpiła awaria?glibc
uc_mcontext
nie zawiera pola o nazwieeip
. Jest teraz tablica, która musi zostać zindeksowana,uc_mcontext.gregs[REG_EIP]
jest równoważna.Mimo że podano prawidłową odpowiedź , która opisuje sposób korzystania z
backtrace()
funkcji GNU libc 1, a ja podałem własną odpowiedź, która opisuje, w jaki sposób zapewnić powrót śledzenia z procedury obsługi sygnału do faktycznej lokalizacji błędu 2 , nie widzę wszelkie wzmianki o demontażu symboli C ++ wyprowadzanych z śledzenia wstecznego.Podczas uzyskiwania śladów zwrotnych z programu C ++ dane wyjściowe można przepuszczać przez
c++filt
1, aby rozplątać symbole lub bezpośrednio 1 .abi::__cxa_demangle
c++filt
i__cxa_demangle
są specyficzne GCCPoniższy przykład C ++ Linux używa tej samej procedury obsługi sygnału, co moja inna odpowiedź i pokazuje, jak
c++filt
można użyć do rozplątania symboli.Kod :
Wyjście (
./test
):Wyjście demontowane (
./test 2>&1 | c++filt
):Poniższy fragment opiera się na procedurze obsługi sygnału z mojej oryginalnej odpowiedzi i może zastąpić procedurę obsługi sygnału w powyższym przykładzie, aby zademonstrować, jak
abi::__cxa_demangle
można użyć do rozplątania symboli. Ta procedura obsługi sygnału wytwarza takie same odkodowane dane wyjściowe jak w powyższym przykładzie.Kod :
źródło
std::cerr
,free()
aexit()
wszystko naruszać ograniczeń wobec dzwoni non-asynchroniczny sygnał bezpieczne połączenia w systemach POSIX. Kod ten impas, jeśli proces nie powiedzie się w każdym wywołaniu takich jakfree()
,malloc()
new
lubdetete
.Warto przyjrzeć się Google Breakpad , wieloplatformowemu generatorowi zrzutów awaryjnych i narzędziom do przetwarzania zrzutów.
źródło
Nie określiłeś systemu operacyjnego, więc trudno jest na nie odpowiedzieć. Jeśli używasz systemu opartego na gnu libc, być może będziesz mógł skorzystać z funkcji libc
backtrace()
.GCC ma również dwa wbudowane narzędzia, które mogą ci pomóc, ale które mogą, ale nie muszą być w pełni zaimplementowane w twojej architekturze, i są
__builtin_frame_address
i__builtin_return_address
. Oba chcą bezpośredniego poziomu liczb całkowitych (przez natychmiastowe, to znaczy, że nie może to być zmienna). Jeśli__builtin_frame_address
dla danego poziomu nie jest zero, należy bezpiecznie pobrać adres zwrotny tego samego poziomu.źródło
Dziękuję entuzjastycznie za zwrócenie mojej uwagi na narzędzie addr2line.
Napisałem szybki i brudny skrypt do przetworzenia wyniku odpowiedzi podanej tutaj : (wielkie dzięki dla jschmier!) Za pomocą narzędzia addr2line.
Skrypt akceptuje jeden argument: nazwę pliku zawierającego dane wyjściowe z narzędzia jschmier.
Dane wyjściowe powinny wypisać coś takiego dla każdego poziomu śledzenia:
Kod:
źródło
ulimit -c <value>
ustawia limit rozmiaru rdzenia pliku na Uniksie. Domyślnie limit rozmiaru podstawowego pliku wynosi 0. Możesz zobaczyć swojeulimit
wartości za pomocąulimit -a
.Ponadto, jeśli uruchomisz program z poziomu gdb, zatrzyma on Twój program w przypadku „naruszeń segmentacji” (
SIGSEGV
zazwyczaj, gdy uzyskasz dostęp do fragmentu pamięci, który nie został przydzielony) lub możesz ustawić punkty przerwania.ddd i nemiver to nakładki na gdb, które znacznie ułatwiają pracę z nim nowicjuszowi.
źródło
Ważne jest, aby pamiętać, że po wygenerowaniu pliku podstawowego należy użyć narzędzia gdb, aby na niego spojrzeć. Aby gdb zrozumiał twój plik podstawowy, musisz powiedzieć gcc, aby instrumentował plik binarny za pomocą symboli debugowania: aby to zrobić, skompiluj flagę -g:
Następnie możesz ustawić „ulimit -c unlimited”, aby zrzucił rdzeń, lub po prostu uruchomić program w gdb. Drugie podejście bardziej mi się podoba:
Mam nadzieję, że to pomoże.
źródło
gdb
bezpośrednio z programu do zawieszania się. Procedura obsługi SIGSEGV, SEGILL, SIGBUS, SIGFPE, która wywoła gdb. Szczegóły: stackoverflow.com/questions/3151779/… Zaletą jest to, że otrzymujesz piękne, opatrzone adnotacjamibt full
ślady wstecz, jak w , a także możesz uzyskać ślady stosu wszystkich wątków.Przez jakiś czas przyglądałem się temu problemowi.
I pochowany głęboko w README Narzędzi Google
http://code.google.com/p/google-perftools/source/browse/trunk/README
mówi o libunwind
http://www.nongnu.org/libunwind/
Bardzo chciałbym usłyszeć opinie o tej bibliotece.
Problem z -rdynamic polega na tym, że w niektórych przypadkach może on znacznie zwiększyć rozmiar pliku binarnego
źródło
Niektóre wersje libc zawierają funkcje, które radzą sobie ze śladami stosu; możesz ich użyć:
http://www.gnu.org/software/libc/manual/html_node/Backtraces.html
Pamiętam, jak dawno temu korzystałem z libunwind, aby uzyskać ślady stosu, ale może nie być obsługiwany na twojej platformie.
źródło
Możesz użyć DeathHandler - małej klasy C ++, która robi wszystko za Ciebie, niezawodnie.
źródło
execlp()
do wykonywania wywołań addr2line ... fajnie byłoby w pełni pozostać we własnym programie (co jest możliwe dzięki dołączeniu kodu addr2line w jakiejś formie)Zapomnij o zmianie źródeł i zrób kilka hacków z funkcją backtrace () lub makrami - to tylko kiepskie rozwiązania.
Jako właściwie działające rozwiązanie doradziłbym:
Spowoduje to wydrukowanie odpowiedniego, czytelnego śladu wstecznego programu w sposób czytelny dla człowieka (z nazwami plików źródłowych i numerami wierszy). Co więcej, takie podejście zapewni Ci swobodę automatyzacji systemu: przygotuj krótki skrypt, który sprawdza, czy proces utworzył zrzut pamięci, a następnie wysyłaj ślady za pośrednictwem poczty e-mail do programistów lub zaloguj się do jakiegoś systemu logowania.
źródło
jest zmienną systemową, która pozwoli na utworzenie zrzutu podstawowego po awarii aplikacji. W tym przypadku nieograniczona ilość. Poszukaj pliku o nazwie core w tym samym katalogu. Upewnij się, że skompilowałeś swój kod z włączonymi informacjami debugowania!
pozdrowienia
źródło
limit coredumpsize unlimited
Patrzeć na:
man 3 backtrace
I:
Są to rozszerzenia GNU.
źródło
Zobacz funkcję śledzenia stosu w ACE (ADAPTIVE Communication Environment). Jest już napisany, aby objąć wszystkie główne platformy (i więcej). Biblioteka jest licencjonowana w stylu BSD, więc możesz nawet kopiować / wklejać kod, jeśli nie chcesz używać ACE.
źródło
Mogę pomóc w wersji dla systemu Linux: można użyć funkcji backtrace, backtrace_symbols i backtrace_symbols_fd. Zobacz odpowiednie strony podręcznika.
źródło
Wygląda na to, że w jednej z ostatnich wersji doładowania c ++ pojawiła się biblioteka zapewniająca dokładnie to, czego chcesz, prawdopodobnie kod byłby wieloplatformowy. Jest to boost :: stacktrace , którego możesz używać jak w próbce boost :
W systemie Linux kompilujesz powyższy kod:
Przykład śledzenia wstecznego skopiowanego z dokumentacji doładowania :
źródło
* nix: możesz przechwycić SIGSEGV (zwykle ten sygnał jest podnoszony przed awarią) i przechowywać informacje w pliku. (oprócz podstawowego pliku, którego można użyć do debugowania na przykład przy użyciu gdb).
win: sprawdź to z msdn.
Możesz także spojrzeć na kod chrome Google, aby zobaczyć, jak radzi sobie z awariami. Ma ładny mechanizm obsługi wyjątków.
źródło
Odkryłem, że rozwiązanie @tgamblin nie jest kompletne. Nie można go obsłużyć przy przepełnieniu stosu. Myślę, że ponieważ domyślnie procedura obsługi sygnału jest wywoływana z tym samym stosem, a SIGSEGV jest wyrzucany dwukrotnie. Aby go chronić, musisz zarejestrować niezależny stos dla procedury obsługi sygnału.
Możesz to sprawdzić za pomocą kodu poniżej. Domyślnie procedura obsługi kończy się niepowodzeniem. Przy zdefiniowanym makrze STACK_OVERFLOW wszystko jest w porządku.
źródło
Nowy król w mieście przybył https://github.com/bombela/backward-cpp
1 nagłówek do umieszczenia w kodzie i 1 biblioteka do zainstalowania.
Osobiście nazywam to za pomocą tej funkcji
źródło
Użyłbym kodu, który generuje ślad stosu dla wycieku pamięci w Visual Leak Detector . Działa to jednak tylko w systemie Win32.
źródło
Widziałem tutaj wiele odpowiedzi wykonujących procedurę obsługi sygnału, a następnie wychodzących. Tak należy postępować, ale pamiętaj o bardzo ważnym fakcie: jeśli chcesz uzyskać zrzut pamięci dla wygenerowanego błędu, nie możesz zadzwonić
exit(status)
. Zadzwońabort()
zamiast!źródło
Jako rozwiązanie tylko dla systemu Windows można uzyskać ekwiwalent śledzenia stosu (z dużo, znacznie więcej informacji) za pomocą Raportowania błędów systemu Windows . Za pomocą zaledwie kilku wpisów rejestru można skonfigurować zbieranie zrzutów w trybie użytkownika :
Wpisy rejestru można ustawić za pomocą instalatora, który ma wymagane uprawnienia.
Utworzenie zrzutu trybu użytkownika ma następujące zalety w porównaniu do generowania śledzenia stosu na kliencie:
Zauważ, że WER może być wyzwalany tylko przez awarię aplikacji (tj. System kończący proces z powodu nieobsługiwanego wyjątku).
MiniDumpWriteDump
można wywołać w dowolnym momencie. Może to być pomocne, jeśli musisz zrzucić bieżący stan, aby zdiagnozować problemy inne niż awaria.Obowiązkowa lektura, jeśli chcesz ocenić możliwość zastosowania mini-zrzutu:
źródło
Oprócz powyższych odpowiedzi, tutaj możesz dowiedzieć się, jak sprawić, by Debian Linux OS generował zrzut pamięci
źródło
Jeśli nadal chcesz iść sam, tak jak ja, możesz połączyć się z linkiem
bfd
i unikać używaniaaddr2line
tego, co zrobiłem tutaj:https://github.com/gnif/LookingGlass/blob/master/common/src/crash.linux.c
To daje wynik:
źródło
W systemach Linux / unix / MacOSX używaj plików podstawowych (możesz włączyć je za pomocą ulimit lub kompatybilnego wywołania systemowego ). W systemie Windows używaj raportowania błędów Microsoft (możesz zostać partnerem i uzyskać dostęp do danych awarii aplikacji).
źródło
Zapomniałem o technologii „apport” w GNOME, ale niewiele wiem o jej używaniu. Służy do generowania śladów stosu i innych danych diagnostycznych do przetwarzania i może automatycznie zgłaszać błędy. Na pewno warto się zameldować.
źródło