Stacktrace z miejsca wystąpienia crasha a nie z crashHandlera

0

Hej mam taki problem

Uzywam boost::stacktrace na minGw 4.8.2 i Qt 5.3.1
Napisałem mały przykład ktory poprawnie generuje stacktrace funkcje sie wyswietlają. Tu jest mój post w którym opisałem jak to uruchomiłem.

#include <QCoreApplication>

#include <signal.h>
#include <cstdlib>
#include <iostream>

#define BOOST_STACKTRACE_USE_BACKTRACE
#include "boost/stacktrace.hpp"

void func2()
{
    boost::stacktrace::stacktrace st;
    std::cout << "Crashed:\n" << st << std::endl;
}

void func1()
{
    func2();
}

void my_signal_handler(int signum)
{
    ::signal(signum, SIG_DFL);
    func1();
}

void crash()
{
    throw std::runtime_error("test");
}

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    ::signal(SIGSEGV, &my_signal_handler);
    ::signal(SIGABRT, &my_signal_handler);

    crash();

    return a.exec();
}

Tu mamy wynik działania:

//terminate called after throwing an instance of 'std::runtime_error'
// what(): test
//Crashed:
// 0# boost::basic_stacktrace<std::allocatorboost::stacktrace::frame >::init(unsigned int, unsigned int) at ../../boost/boost/stacktrace/stacktrace.hpp:75
// 1# func2() at ../stc_mingw482/main.cpp:16
// 2# func1() at ../stc_mingw482/main.cpp:23
// 3# my_signal_handler(int) at ../stc_mingw482/main.cpp:29
// 4# _imp___set_invalid_parameter_handler in C:\Windows\System32\msvcrt.dll
// 5# _imp___set_invalid_parameter_handler in C:\Windows\System32\msvcrt.dll
// 6# _imp___set_invalid_parameter_handler in C:\Qt\Qt5.3.1_MGW\5.3\mingw482_32\bin\libstdc++-6.dll
// 7# main at ../stc_mingw482/main.cpp:44
// 8# __tmainCRTStartup in C:\stc_mingw482_qt571\build-stc_mingw482-Desktop_Qt_5_3_MinGW_32bit-Debug\debug\stc_mingw482.exe
// 9# _imp___set_invalid_parameter_handler in C:\Windows\System32\KERNEL32.DLL
//10# _imp___set_invalid_parameter_handler in C:\Windows\SYSTEM32\ntdll.dll
//11# _imp___set_invalid_parameter_handler in C:\Windows\SYSTEM32\ntdll.dll

I tu zaczyna się moje pytanie.
Czy jest możliwe w jakiś sposób złapanie momentu w którym wystąpił problem w aplikacji?
Czyli w funkcji crash() w tym przypadku. Bo ten przykład drukuje stacktrace ale z poziomu wywołania w func2.
A ja chciałbym mieć efekt podobny do tego co by dało gdb które by pokazało gdzie jest problem a nie generowało stacka/dumpa z miejsca wywołania biblioteki.

5

boost::stacktrace jest przydatne jak potrzebujesz mieć call stack, który będzie dostępny w kodzie produkcyjnym (mało jest takich przypadków).

Lepiej trzymać symbole dla buildów, które idą do testów/klienta i użyć google breakpad lub podobne rozwiązanie. Wtedy dostaniesz zakodowany dump z momentu crash-a, który możesz zdekodować za pomocą zapamiętanych symboli.
Na Posix crash zwykle generuje plik tekstowy z callstack wszystkich wątków zawierające adresy funkcji, które można zamienić na nazwy funkcji i miejsce w kodzie, za pomocą pliku z symbolami.
Na windows możesz mieć pełny lub mini dump aplikacji i można "debuggować" zcrashowaną aplikację, nawet jak crash był na cudzym komputerze. W pełnym dump będziesz widział zmienne, mini dump tylko call stack dla wszystkich wątków.
google breakpad uogólnia to do jednego formatu.

Kiedyś miałem jakąś apkę połączoną z usługą google analytics i crashe z każdej platformy automatem były dekodowane i gromadzone, przez usługę google.
Równocześnie kolekcjonowało to statystki użycia aplikacji (z jakich funkcji korzysta użytkownik, na jakich okienkach przebywa najdłużej itp).

Instrukcje od google jak to zrobić są dość przyjemne.

0

Problem jest taki, że sygnał jest uruchomiony z innym contextem.
Sygnał też dostaje dostęp do kontekstu jaki był w ostatniej chwili działania.

Możesz teoretycznie podmienić kontekst, wykonać backtrace i wyjść gracefully, tyle że nie wiem na ile to jest preparowane compilation time, jakieś elementy pomocnicze do printowania stacktrace przy użyciu dyrektyw preprocesora itp, a tak będzie wszystko dynamicznie zrobione.
Normalnie jak odczytasz stack po adresie, to nie jest tak całkiem prosto skapnąć się czy dana wartość to adres powrotu, cannary, jakieś zmienne, teoretycznie jest alignment i zakresy funkcji znasz to możesz przewidzieć czy wartość należy do zakresu adresu jakiejś funkcji, zmienne i inne pierdoły spłaszczyć, ale dalej może być taki przypadek, że jakaś duża liczba losowa dziwnym trafem będzie należeć do przedziału jakiejś istniejącej funkcji co źle zostanie zinterpretowane.

Jak kontekst zmienisz na poprzedni, ale adres następnej instrukcji ustawisz na ten w handlerze funkcji, która wykona backtrace to możliwe, że jakoś by przeszło, a najwyżej by się wykrzaczyło, ale oczywiście po tym od razu byś musiał zakończyć program jakimś exit(), terminate.

Teoretycznie jeśli wiesz ile błędna operacja ma wielkości i wiesz, że pominięcie jej nie spowoduje problemu to możesz do RIP dodać wielkość o tą instrukcję i kontynuować kontekst od tego miejsca.

Na windowsie się na to mówi thread hijacking jeśli to robimy z zewnątrz procesu.

1

ja czasami używam https://github.com/bombela/backward-cpp
dla przykładowej funkcji crash wynik

Stack trace (most recent call last):
#5    Object "", at 00007FF96A1B257D, in BaseThreadInitThunk
#4    Object "", at 00007FF6C2581366, in mainCRTStartup
#3    Object "", at 00007FF6C2581315, in WinMainCRTStartup
#2    Source "...main.cpp", line 504, in main [00007FF6C259FBCF]
        501: {
        502:
        503:   crash();
      > 504:   printf("buuu\n");
        505:
        506:
        507:     // TODO ....
#1    Source "...main.cpp", line 497, in crash [00007FF6C259FB62]
        495: void crash()
        496: {
      > 497:     throw std::runtime_error("test");
        498: }
        499:
        500: int main(int argc, char *argv[])
#0    Object "", at 00007FF8EEA9296C, in _cxa_throw

ale ja to używam z msys2 + clang , na gcc nie zadziała

0

Dzięki za zainteresowanie tematem.

@tumor: jak byś mógł mnie naprowadzić na to jak uzyskać context właśnie z jak to ująłeś "ostatniej chwili działania" byłoby super.

To mogłoby pozwolić mi na uzyskanie właśnie tego co jest mi potrzebne.

@Marius.Maximus: dzięki za propozycję ale niestety mam narzucone gcc więc to rozwiązanie odpada.

@MarekR22: Dzięki poczytam o zaproponowanym rozwiązaniu.

0

@jwp1: tak z ciekawości dlaczego taka stara wersja Qt i c++ ?

0

Nie wiem jak z QT, ale na czysto linuxowych operacjach na sigaction byś to wykonał, zmiana kontekstu mógłbyś wykonać zwykłym przypisaniem wartości wszystkich rejestrów i ostatni przypisać RIP gdyż wtedy cię wywali w inne miejsce kodu.

QT jest multiplatformowy, a taki sigaction jest linux only więc to pewnie nie zadziała przy kompilacjach na windowsa itp.

0

@Marius.Maximus: Dobre pytanie. Taką niestety mam jeszcze w firmie. Wiem masakra. Sporo czasu mi zajęło przepchnięcie tematu upgradu. Ale teraz już mam to na wokandzie.

1

Użyj breakpad/crashpad (crashpad lepszy, bo out of process, możesz użyć forka Backtrace żeby zbudować go z CMake: https://github.com/backtrace-labs/crashpad), on ma to wszystko ogarnięte ładnie.

4

@kq: crashpad to chyba bedzie problem zbudować w mingw bo w wersji na Windows widzę np. #include <../ucrt/time.h> a tego brak w mingw

1

W teorii powinien się dać skompilować, ale pewnie wymagałoby to trochę dopieszczenia. Szkoda, że nie miałem czasu nigdy się tym zająć.

0

@kq: dzięki za propozycje ale tak się temu przyglądam i to jest chyba przygotowane pod kompilator msvc a nie mingw. Ale poćwiczę z tym. Zainstaluje sobie msvc żeby zobaczyć jak to działa. No i potwierdzę lub nie czy da się to zbudować na mingw bo @Marius.Maximus sugeruje że nie.

Znalazłem cos takiego https://github.com/jrfonseca/drmingw. Działa to podobnie jak gdb, ale z opisu może mieć zaimplementowaną funkcjonalność o którą mi chodzi lub naprowadzić na jakieś rozwiązanie. Projekt ten w related links ma podane linki do breakpad/crashpad wiec coś może tu być na rzeczy. Zatem "walki ciąg dalszy".

@tumor: wydaje mi się libunwind jest dedykowany dla Linuxa.

0

@jwp1 teorertycznie ja wiem jak sparsować stos, żeby dostać ładny print, ale może być taki problem, że jakaś zmienna gdzieś czy tekst akurat będzie razem reprezentować liczbę, która akurat będzie w przedziale dnej funckji i wtedy po prostu źle parser zinterpretuje.

A tak czemu nie to w miarę proste jest do zrobienia.
Wystarczy adres RSP znać i się wszystko obliczy.

0

@tumor Ja robię coś takiego pierwszy raz. Nie mam kogo się podpytać w firmie bo wszyscy pracują na czystym embedded. Dlatego tu napisałem. Nie proszę o gotowe rozwiązanie tylko o info ewentualnie przykład jak się dobrać do stosu na windowsie (z mingw).
Po szperaniu w necie wydaje się to być wiedzą tajemną bo nie wiele jest info dla Windowsa (przynajmniej ja nie znalazłem za dużo lub zadałem złe pytania do wujka googla), za to dla Linuxa znalazłem całą masę przykładów jak zrobić coś takiego. Nie proszę o gotowe rozwiązanie bo wtedy nie będzie zabawy i satysfakcji że zrobiło się coś samemu. Tylko o wędkę a ja sam sobie ryb nałapię. Zatem jak wiesz, jak sparsować stos lub znasz jakieś przydatne linki z innych forów z infem na ten temat to będę wdzięczny.

Jutro siądę do chatGPT i przyjrzę się bliżej twojemu linkowi z funkcjami.

0

Ma ktoś jakieś inne pomysły.

1 użytkowników online, w tym zalogowanych: 0, gości: 1