Jak mogę złapać SIGSEGV (błąd segmentacji) i uzyskać ślad stosu pod JNI na Androidzie?

92

Przenoszę projekt do nowego zestawu Android Native Development Kit (tj. JNI) i chciałbym złapać SIGSEGV, gdyby to się stało (prawdopodobnie również SIGILL, SIGABRT, SIGFPE), aby przedstawić ładne okno dialogowe raportowania awarii, zamiast (lub wcześniej) co się obecnie dzieje: natychmiastowa bezceremonialna śmierć procesu i prawdopodobnie próba ponownego uruchomienia go przez system operacyjny. ( Edycja: maszyna wirtualna JVM / Dalvik przechwytuje sygnał i rejestruje ślad stosu i inne przydatne informacje; chcę tylko zaoferować użytkownikowi opcję wysłania tych informacji e-mailem).

Sytuacja jest taka: duża część kodu C, którego nie napisałem, wykonuje większość pracy w tej aplikacji (cała logika gry) i chociaż jest dobrze przetestowany na wielu innych platformach, jest całkowicie możliwe, że ja w moim Androidzie port, prześle go do śmieci i spowoduje awarię w kodzie natywnym, więc chcę, aby zrzuty awaryjne (zarówno natywne, jak i Java), które są obecnie wyświetlane w dzienniku Androida (myślę, że byłby to stderr w sytuacji innej niż Android). Mogę dowolnie modyfikować zarówno kod C, jak i Java, chociaż wywołania zwrotne (zarówno wchodzące, jak i wychodzące z JNI) mają około 40 i oczywiście punkty bonusowe za małe różnice.

Słyszałem o bibliotece łańcuchów sygnałów w J2SE, libjsig.so, i gdybym mógł bezpiecznie zainstalować taką obsługę sygnału w systemie Android, rozwiązałoby to chwytliwą część mojego pytania, ale nie widzę takiej biblioteki dla Androida / Dalvik .

Chris Boyle
źródło
Jeśli możesz uruchomić maszynę wirtualną Java za pomocą skryptu opakowania, możesz sprawdzić, czy aplikacja zakończyła się nieprawidłowo, i wykonać raportowanie błędów. Pozwoliłoby to na łatwe wyłapanie wszelkiego rodzaju nieprawidłowych wyjść, czy to SIGSEGV, SIGKILL czy cokolwiek innego. Jednak nie sądzę, aby było to możliwe w przypadku standardowych aplikacji na Androida, więc zamieszczam to jako komentarz (przekonwertowany z odpowiedzi).
sleske
Zobacz także: Nie można uruchomić programu Java dla systemu Android z Valgrind, aby dowiedzieć się, jak uruchomić aplikację na Androida za pomocą skryptu opakowania (w powłoce adb).
sleske
1
Odpowiedź wymaga aktualizacji. Kod źródłowy podany w zaakceptowanej odpowiedzi spowoduje niezdefiniowane zachowanie z powodu wywołania funkcji bezpiecznych dla sygnału asynchronicznego. Zobacz tutaj: stackoverflow.com/questions/34547199/ ...
user1506104

Odpowiedzi:

82

Edycja: począwszy od Jelly Bean nie można uzyskać śladu stosu, ponieważ READ_LOGSodszedł . :-(

Właściwie mam moduł obsługi sygnału działający bez robienia niczego zbyt egzotycznego i wydałem kod, używając go, co można zobaczyć na github (edycja: link do wydania historycznego; od tego czasu usunąłem obsługę awarii). Oto jak:

  1. Służy sigaction()do przechwytywania sygnałów i przechowywania starych uchwytów. ( android.c: 570 )
  2. Czas płynie, zdarza się segfault.
  3. W programie obsługi sygnału, wywołaj JNI ostatni raz, a następnie wywołaj starego handlera. ( android.c: 528 )
  4. W tym wywołaniu JNI zarejestruj wszelkie przydatne informacje dotyczące debugowania i wywołaj startActivity()działanie, które jest oznaczone jako musi znajdować się we własnym procesie. ( SGTPuzzles.java:962 , AndroidManifest.xml: 28 )
  5. Kiedy wrócisz z Javy i wywołasz ten stary program obsługi, platforma Android połączy się, debuggerdaby zarejestrować ładny natywny ślad, a następnie proces umrze. ( debugger.c , debuggerd.c )
  6. W międzyczasie rozpoczyna się Twoja działalność związana z obsługą zderzeń. Naprawdę powinieneś przekazać mu PID, aby mógł poczekać na zakończenie kroku 5; Ja tego nie robię. Tutaj przepraszasz użytkownika i pytasz, czy możesz wysłać dziennik. Jeśli tak, zbierz dane wyjściowe logcat -d -v threadtimei uruchom wiadomość ACTION_SENDz odbiorcą, tematem i treścią wypełnioną. Użytkownik będzie musiał nacisnąć Wyślij. ( CrashHandler.java , SGTPuzzles.java:462 , strings.xml: 41
  7. Uważaj na logcatawarie lub zajęcie więcej niż kilka sekund. Spotkałem jedno urządzenie, T-Mobile Pulse / Huawei U8220, w którym logcat natychmiast przechodzi w stan T(śledzony) i zawiesza się. ( CrashHandler.java:70 , strings.xml : 51 )

W sytuacji bez Androida niektóre z nich byłyby inne. Musiałbyś zebrać swój własny natywny ślad, zobacz to inne pytanie , w zależności od rodzaju posiadanej biblioteki libc. Musiałbyś poradzić sobie z zrzuceniem tego śladu, uruchomieniem oddzielnego procesu obsługi awarii i wysłaniem wiadomości e-mail w odpowiedni sposób dla Twojej platformy, ale wyobrażam sobie, że ogólne podejście powinno nadal działać.

Chris Boyle
źródło
2
Idealnie byłoby sprawdzić, czy awaria wystąpiła w Twojej bibliotece. Jeśli zdarzyło się to gdzieś indziej (powiedzmy, wewnątrz maszyny wirtualnej), wywołania JNI z modułu obsługi sygnału mogą raczej źle zmylić. To nie koniec świata, ponieważ i tak jesteś w połowie awarii, ale może to utrudnić diagnozę awarii maszyny wirtualnej (lub spowodować dziwną awarię maszyny wirtualnej, która kończy się w raporcie o błędzie Androida i zaskakuje wszystkich).
fadden
Jesteś wspaniały @Chris za udostępnienie swojego projektu badawczego na ten temat!
olafure
Dzięki, to było przydatne w znalezieniu, gdzie moje JNI szaleje. Witam również absolwenta DCS!
Nick
3
Uruchomienie działania w nowym procesie z usługi wymaga również następującego kodu:newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
Graeme
1
Czy to rozwiązanie nadal obowiązuje pod Jelly Bean? Czy w kroku 6 nie uda się zarejestrować żadnych debuggerdwyników?
Josh
14

Jestem trochę późno, ale miałem dokładnie taką samą potrzebę, i ja stworzyliśmy małą bibliotekę, aby go rozwiązać, łapiąc typowych awarii ( SEGV, SIBGUSitp) wewnątrz kodu JNI , i zastąpić je regularnie java.lang.Error wyjątkami . Dodatkowo, jeśli klient działa na systemie Android> = 4.1.1, ślad stosu osadza rozwiązany ślad wstecznej awarii (pseudo-ślad zawierający pełny natywny ślad stosu). Nie odzyskasz sprawności po błędnych awariach (np. Jeśli uszkodzisz alokator), ale przynajmniej powinno to pozwolić ci na odzyskanie większości z nich. (prosimy o zgłaszanie sukcesów i porażek, kod jest nowy)

Więcej informacji na https://github.com/xroche/coffeecatch (kod to licencja BSD 2-Clauses )

xroche
źródło
6

FWIW, Google Breakpad działa dobrze na Androidzie. Wykonałem prace związane z przenoszeniem i wysyłamy to jako część Firefoksa Mobile. Wymaga niewielkiej konfiguracji, ponieważ nie daje śladów stosu po stronie klienta, ale wysyła nieprzetworzoną pamięć stosu i wykonuje spacer po stosie po stronie serwera (więc nie musisz wysyłać symboli debugowania z aplikacją ).

Ted Mielczarek
źródło
1
Skonfigurowanie Breakpada jest prawie niemożliwe, biorąc pod uwagę brakującą dokumentację
shader
To naprawdę nie jest takie trudne, a na wiki projektu jest mnóstwo dokumentacji. W rzeczywistości dla Androida jest teraz plik Makefile kompilacji NDK i powinien być bardzo łatwy w użyciu: code.google.com/p/google-breakpad/source/browse/trunk/ ...
Ted Mielczarek
Musisz także skompilować moduł, który wstępnie przetwarza pliki symboli debugowania dla Androida i możesz to skompilować tylko w systemie Linux. Podczas kompilacji na komputerze Mac - buduje on tylko preprocesor dSym dla systemu Mac / iOS.
shader,
5

Z mojego ograniczonego doświadczenia (inne niż Android), SIGSEGV w kodzie JNI generalnie spowoduje awarię JVM, zanim kontrola zostanie zwrócona do kodu Java. Mglisto przypominam sobie, że słyszałem o jakiejś innej maszynie JVM innej niż Sun, która pozwala złapać SIGSEGV, ale AFAICR nie możesz oczekiwać, że będziesz w stanie to zrobić.

Możesz spróbować złapać je w C (patrz sigaction (2)), chociaż możesz zrobić bardzo niewiele po obsłudze SIGSEGV (lub SIGFPE lub SIGILL), ponieważ bieżące zachowanie procesu jest oficjalnie nieokreślone.

mas90
źródło
Cóż, zachowanie jest niezdefiniowane po „zignorowaniu sygnału SIGFPE, SIGILL lub SIGSEGV, który nie został wygenerowany przez kill (2) lub raise (3)”, ale niekoniecznie podczas przechwytywania takiego sygnału. Obecny plan polega na wypróbowaniu programu obsługi sygnału w języku C, który odwołuje się do języka Java i w jakiś sposób kończy wątek bez kończenia procesu. To może być możliwe lub nie. :-)
Chris Boyle
1
Instrukcje dotyczące śledzenia wstecznego C: stackoverflow.com/questions/76822/…
Chris Boyle,
1
... poza tym, że nie mogę używać funkcji backtrace (), ponieważ Android nie używa glibc, używa Bionic. :-( Zamiast tego potrzebne będzie coś związanego _Unwind_Backtracez od unwind.h.
Chris Boyle,