2017-01-24 51 views
11

Mam aplikacja, która strumieni wideo za pomocą Kickflip i ButterflyTV libRTMPJak debugować SEGV_ACCERR

Teraz do 99% procent czasu aplikacja pracuje ok, ale od czasu do czasu dostaję natywną winy segmentacji, że jestem nie jest w stanie do debugowania, ponieważ wiadomości są zbyt tajemnicze:

01-24 10:52:25.576 199-199/? A/DEBUG: *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** 
01-24 10:52:25.576 199-199/? A/DEBUG: Build fingerprint: 'google/hammerhead/hammerhead:6.0.1/M4B30Z/3437181:user/release-keys' 
01-24 10:52:25.576 199-199/? A/DEBUG: Revision: '11' 
01-24 10:52:25.576 199-199/? A/DEBUG: ABI: 'arm' 
01-24 10:52:25.576 199-199/? A/DEBUG: pid: 14302, tid: 14382, name: MuxerThread >>> tv.myapp.broadcast.dev <<< 
01-24 10:52:25.576 199-199/? A/DEBUG: signal 11 (SIGSEGV), code 2 (SEGV_ACCERR), fault addr 0x9fef1000 
01-24 10:52:25.636 199-199/? A/DEBUG: Abort message: 'Setting to ready!' 
01-24 10:52:25.636 199-199/? A/DEBUG:  r0 9c6f9500 r1 9c6f94fc r2 9fee900c r3 00007ff4 
01-24 10:52:25.636 199-199/? A/DEBUG:  r4 9fee9010 r5 9fef0ffd r6 00007ff1 r7 9fef0d88 
01-24 10:52:25.636 199-199/? A/DEBUG:  r8 cfe40980 r9 9e0a6900 sl 00007ff4 fp 9c6f94fc 
01-24 10:52:25.636 199-199/? A/DEBUG:  ip 9c6f9058 sp 9c6f94dc lr 000000e9 pc b3a33cb6 cpsr 800f0030 
01-24 10:52:25.650 199-199/? A/DEBUG: backtrace: 
01-24 10:52:25.651 199-199/? A/DEBUG:  #00 pc 00004cb6 /data/app/tv.myapp.broadcast.dev-2/lib/arm/librtmp-jni.so 
01-24 10:52:25.651 199-199/? A/DEBUG:  #01 pc 00005189 /data/app/tv.myapp.broadcast.dev-2/lib/arm/librtmp-jni.so (rtmp_sender_write_video_frame+28) 
01-24 10:52:25.651 199-199/? A/DEBUG:  #02 pc 00005599 /data/app/tv.myapp.broadcast.dev-2/lib/arm/librtmp-jni.so (Java_net_butterflytv_rtmp_1client_RTMPMuxer_writeVideo+60) 
01-24 10:52:25.651 199-199/? A/DEBUG:  #03 pc 014e84e7 /data/app/tv.myapp.broadcast.dev-2/oat/arm/base.odex (offset 0xa66000) (int net.butterflytv.rtmp_client.RTMPMuxer.writeVideo(byte[], int, int, int)+122) 
01-24 10:52:25.651 199-199/? A/DEBUG:  #04 pc 014dbd55 /data/app/tv.myapp.broadcast.dev-2/oat/arm/base.odex (offset 0xa66000) (void io.kickflip.sdk.av.muxer.RtmpMuxerMix.writeThread()+2240) 
01-24 10:52:25.651 199-199/? A/DEBUG:  #05 pc 014d8c41 /data/app/tv.myapp.broadcast.dev-2/oat/arm/base.odex (offset 0xa66000) (void io.kickflip.sdk.av.muxer.RtmpMuxerMix.access$000(io.kickflip.sdk.av.muxer.RtmpMuxerMix)+60) 
01-24 10:52:25.651 199-199/? A/DEBUG:  #06 pc 014d819f /data/app/tv.myapp.broadcast.dev-2/oat/arm/base.odex (offset 0xa66000) (void io.kickflip.sdk.av.muxer.RtmpMuxerMix$1.run()+98) 
01-24 10:52:25.651 199-199/? A/DEBUG:  #07 pc 721e78d1 /data/dalvik-cache/arm/[email protected]@boot.oat (offset 0x1ed6000) 

Ponownie, w strumieniu 2 godziny to nie może się wydarzyć lub może się zdarzyć w 10 minucie strumienia. Bardzo ciężko jest debugować, ponieważ nie mogę wymusić błędu.

Czy istnieje sposób na poprawę informacji o debugowaniu, które otrzymuję? Co dokładnie oznacza SEGV_ACCER? Czytałem, że to "oznacza, że ​​próbujesz uzyskać dostęp do adresu, do którego nie masz uprawnień dostępu." ale nie jestem pewien, co to oznacza, ponieważ mogę przesyłać strumieniowo wiele godzin bez wystąpienia błędu.

Czy istnieje sposób, aby złapać sygnał i po prostu kontynuować?

EDIT: aby dodać więcej informacji, jest to część rodzimej biblioteki gdzie awarie aplikacji (znaleziono przy użyciu NDK-stack):

JNIEXPORT jint JNICALL 
Java_net_butterflytv_rtmp_1client_RTMPMuxer_writeVideo(JNIEnv *env, jobject instance, 
                 jbyteArray data_, jint offset, jint length, 
                 jint timestamp) { 
    jbyte *data = (*env)->GetByteArrayElements(env, data_, NULL); 
    jint result = rtmp_sender_write_video_frame(data, length, timestamp, 0, 0); 
    (*env)->ReleaseByteArrayElements(env, data_, data, 0); 

    return result; 
} 


int rtmp_sender_write_video_frame(uint8_t *data, 
            int size, 
            uint64_t dts_us, 
            int key, 
            uint32_t abs_ts) 
{ 


    uint8_t * buf; 
    uint8_t * buf_offset; 
    int val = 0; 
    int total; 
    uint32_t ts; 
    uint32_t nal_len; 
    uint32_t nal_len_n; 
    uint8_t *nal; 
    uint8_t *nal_n; 
    char *output ; 
    uint32_t offset = 0; 
    uint32_t body_len; 
    uint32_t output_len; 

    buf = data; 
    buf_offset = data; 
    total = size; 
    ts = (uint32_t)dts_us; 

    //ts = RTMP_GetTime() - start_time; 
    offset = 0; 

    nal = get_nal(&nal_len, &buf_offset, buf, total); 

(...) 


} 



static uint8_t * get_nal(uint32_t *len, uint8_t **offset, uint8_t *start, uint32_t total) 
{ 
    uint32_t info; 
    uint8_t *q ; 
    uint8_t *p = *offset; 
    *len = 0; 




    if ((p - start) >= total) 
     return NULL; 

    while(1) { 
     info = find_start_code(p, 3); 

     if (info == 1) 
      break; 
     p++; 
     if ((p - start) >= total) 
      return NULL; 
    } 
    q = p + 4; 
    p = q; 

    while(1) { 
     info = find_start_code(p, 3); 

     if (info == 1) 
      break; 
     p++; 
     if ((p - start) >= total) 
      //return NULL; 
      break; 
    } 


    *len = (p - q); 
    *offset = p; 
    return q; 
} 


static uint32_t find_start_code(uint8_t *buf, uint32_t zeros_in_startcode) 
{ 
    uint32_t info; 
    uint32_t i; 

    info = 1; 
    if ((info = (buf[zeros_in_startcode] != 1)? 0: 1) == 0) 
     return 0; 

    for (i = 0; i < zeros_in_startcode; i++) 
     if (buf[i] != 0) 
     { 
      info = 0; 
      break; 
     }; 

    return info; 
} 

Zawieszanie się dzieje w buf[zeros_in_startcode] w find_start_code. Usunąłem także kilka linii android_log (nie sądzę, że to ma znaczenie?).

Według mojego zrozumienia, ten bufor powinien być dostępny, nie ma sensu, że ulega awarii tylko "czasami".

PS. tutaj nazywam kod natywny z Javy:

private void writeThread() { 

     while (true) { 

      Frame frame = null; 
      synchronized (mBufferLock) { 
       if (!mConfigBuffer.isEmpty()) { 
        frame = mConfigBuffer.peek(); 
       } else if (!mBuffer.isEmpty()) { 
        frame = mBuffer.remove(); 
       } 
       if (frame == null) { 
        try { 
         mBufferLock.wait(); 
        } catch (InterruptedException e) { 
        } 
       } 
      } 

      if (frame == null) { 
       continue; 
      } else if (frame instanceof Sentinel) { 
       break; 
      } 


      int writeResult = 0; 

      synchronized (mWriteFence) { 
       if (!mConnected) { 
        debug(WARN, "Skipping frame due to disconnection"); 
        continue; 
       } 

       if (frame.getFrameType() == Frame.VIDEO_FRAME) {    
        writeResult = mRTMPMuxer.writeVideo(frame.getData(), frame.getOffset(), frame.getSize(), frame.getTime()); 
       } else if (frame.getFrameType() == Frame.AUDIO_FRAME) { 
        writeResult = mRTMPMuxer.writeAudio(frame.getData(), frame.getOffset(), frame.getSize(), frame.getTime()); 

       } 

       if (writeResult < 0) { 
         mRtmpListener.onDisconnected(); 
         mConnected = false; 
       } else { 
        //Now we remove the config frame, only if sending was successful! 
        if (frame.isConfig()) { 
         synchronized (mBufferLock) { 
          mConfigBuffer.remove(); 
         } 
        } 
       } 
      } 

     } 

    } 

Pamiętaj, że awarie występują nawet wtedy, gdy w ogóle nie wysyłam dźwięku.

+0

Zgaduję, że przekraczasz swoją tablicę. Czy masz pewność, że podajesz odpowiednią liczbę elementów tablicy? Czy 'length' odpowiada liczbie elementów zwróconych przez' GetArrayLength() '? Jaka jest rzeczywista wartość "danych"? Czy możesz zmodyfikować swój kod JNI, aby jakoś rejestrować lub w inny sposób pokazywać te informacje? –

+0

@AndrewHenle wszystko to jest bardzo trudne do wykrycia, ponieważ dzieje się to 30 razy na sekundę, a zdarzenie dzieje się tylko losowo (i może minąć wiele godzin), mogę spróbować wydrukować te informacje dla każdej ramki, aby znaleźć wartości, gdy nastąpi awaria, ale kod jest wykonywany mniej razy (z powodu spowolnienia logowania), a zatem może upłynąć wiele godzin ... W każdym razie dane nigdy nie są mniejsze niż 3 bajty, do których indeks jest dostępny w newralgicznym punkcie. –

+0

* wszystko to jest bardzo trudne do wymyślenia ... * Tak, bardzo trudno jest wykryć problemy z uszkodzeniem pamięci. Najprostszym rozwiązaniem jest prawdopodobnie napisanie prostego sterownika C dla biblioteki JNI i wykonywanie wywołań funkcji 'rtmp_sender_write_video_frame()' z prostego programu C. Następnie możesz łatwo wykonać takie czynności, jak przeniesienie kodu do systemu Linux i uruchamianie na przykład w Valgrind. Zauważ, że nawet zrobienie tego może nie wykryć błędu uszkodzenia pamięci - JNI jest * bardzo * bezlitosny, a uszkodzenie pamięci w dowolnym miejscu może spowodować awarie w pozornie całkowicie niepowiązanym kodzie. –

Odpowiedz

4

„można przechowywać dane w byte[]. Pozwala to na bardzo szybki dostęp z udało kod. Na rodzimym strony jednak, nie jesteś gwarancją w stanie uzyskać dostęp do danych bez konieczności kopiowania to."

Zobacz https://developer.android.com/training/articles/perf-jni.html

Analysis

Niektóre rozważania i rzeczy, aby spróbować:

  • Kod gdzie przewraca jest bardzo ogólne, więc prawdopodobnie nie istnieje bug
  • It musi być frame dane zostały usunięte/uszkodzone/zablokowane/przeniesione
  • Czy usunięto moduł do usuwania śmieci Java OR przeniesiono dane?
  • Możesz napisać szczegółowe debugowanie do pliku, zastępując go na każdej ramce , więc masz tylko mały dziennik z ostatnimi informacjami debugowania.
  • wysłać lokalną kopię frame zmiennej informacji (za pomocą ByteBuffer) do mRTMPMuxer.writeVideo
    przeciwieństwie do zwykłych byte buforów, w ByteBuffer przechowywanie nie jest alokowana na zarządzanym heap i może zawsze jest dostępny bezpośrednio z natywnego kodu.

Realizacja

//allocates memory from the native heap 
ByteBuffer data = ByteBuffer.allocateDirect(frame.getData().length); 
data.clear(); 
//System.gc(); 
//copy data 
data.get(frame.getData(), 0, frame.getData().length); 
//data = (frame.getData() == null) ? null : frame.getData().clone(); 
int offset = frame.getOffset(); 
int size = frame.getSize(); 
int time = frame.getTime(); 
writeResult = mRTMPMuxer.writeVideo(data , offset, size, time); 

JNIEXPORT jint JNICALL 
Java_net_butterflytv_rtmp_1client_RTMPMuxer_writeVideo(
    JNIEnv *env, 
    jobject instance, 
    jobject data_, //NOT jbyteArray data_, 
    jint offset, 
    jint length, 
    jint timestamp) 
{ 
    jbyte *data = env->GetDirectBufferAddress(env, data);//GetDirectBufferAddress NOT GetByteArrayElements 
    jint result = rtmp_sender_write_video_frame(data, length, timestamp, 0, 0); 
    //(*env)->ReleaseByteArrayElements(env, data_, data, 0);//???? 
    return result; 
} 

debugowanie

Niektóre kod z SO Catching exceptions thrown from native code:

static uint32_t find_start_code(uint8_t *buf, uint32_t zeros_in_startcode){ 
    //... 
    try { 
     if ((info = (buf[zeros_in_startcode] != 1)? 0: 1) == 0) return 0;//your code 
    } 
    // You can catch std::exception for more generic error handling 
    catch (std::exception e){ 
     throwJavaException (env, e.what());//see method below 
    } 
    //... 

Potem nowa metoda:

void throwJavaException(JNIEnv *env, const char *msg) 
    { 
    // You can put your own exception here 
    jclass c = env->FindClass("java/lang/RuntimeException"); 
    if (NULL == c) 
    { 
     //B plan: null pointer ... 
     c = env->FindClass("java/lang/NullPointerException"); 
    } 
    env->ThrowNew(c, msg); 
    } 
} 

nie zbyt rozłączyła się SEGV_ACCERR masz winy segmentacji, SIGSEGV (spowodowane przez program próbuje odczytać lub zapisać nieprawidłową lokalizację w pamięci, czytać w danym przypadku).
Od siginfo.h:

SEGV_MAPERR oznacza, że ​​próbujesz uzyskać dostęp do adresu, który nie jest mapowany na nic. SEGV_ACCERR oznacza, że ​​próbowałeś uzyskać dostęp do adresu, do którego nie masz uprawnień dostępu.

Inne

To może być interesujące:

Q: Zauważyłem, że nie było wsparcie RTMP. Ale łatka, która usuwa RTMP została scalona.
P: Czy możesz mi powiedzieć, dlaczego?
A: Nie sądzimy, że RTMP obsługuje przypadek użycia transmisji mobilnych, a także HLS,
A: i dlatego nie chcemy poświęcać naszych ograniczonych zasobów na wsparcie dla .

see: https://github.com/Kickflip/kickflip-android-sdk/issues/33

Proponuję zarejestrować problem z:
https://github.com/Kickflip/kickflip-android-sdk/issues
https://github.com/ButterflyTV/LibRtmp-Client-for-Android/issues

+0

Dziękuję za odpowiedź. W jaki sposób zarządzanie pamięcią działa pomiędzy java i JNI? Czy bufor może być GC'd, jeśli nadal istnieje odniesienie w JNI? Czytałem te problemy dotyczące RTMP w Kickflip, kiedy nie korzystałem z ich (wadliwej) implementacji RTMP z powrotem w ciągu dnia i przerzuciłem się na lekką ButterflyTV. –

+0

Mam nadzieję, że JVM nie GC jest tablicą, która została przekazana do wywołania JNI, podczas gdy to połączenie jest nadal uruchomione. Zgadzam się jednak, że jakiekolwiek uszkodzenie pamięci może nie występować w opublikowanym kodzie - opublikowany kod może być po prostu ofiarą, a awaria ma miejsce w opublikowanym kodzie, ponieważ jest często nazywana * i * powoduje, że wiele połączeń zależy od dynamicznej pamięci . Innymi słowy, jest to kod najprawdopodobniej przebiegający nad kopalnią lądową zasadzoną przez jakiś inny kawałek kodu. –

+0

@JonGoodwin Muszę wykonać więcej testów, ale wydaje się, że to rozwiązało problem. Jestem jednak sceptyczny, to dziwne, że używanie tablicy bajtowej jest podatne na ponowne wykorzystanie/gromadzenie pamięci ... Zrobię więcej testów i zaznaczę odpowiedź (z nagrodą), jeśli wszystko jest pozytywne :) –

1

Według objawów/opis problemu, Twój program jest najprawdopodobniej przeżywa jakiś nieprawidłowy dostęp do pamięci/korupcja , która jest w jakiś sposób powiązana ze scenariuszem wielowątkowej wyścigu. Z mojego doświadczenia wynika, że ​​samo debugowanie uszkodzenia pamięci jest bardzo trudne i jeśli jest połączone z środowiskiem wielowątkowym, staje się bardzo trudne. Niektóre z moich poprzednich postów mogą być pomocne i zawierać ogólne wskazówki na te tematy. Pamiętaj, że te posty dotyczą systemu Windows/Linux i , a nie platformy Android.

cpp - valgrind - Invalid read of size 8

A segmentation fault sometimes occurs when the function cvCreateFileCapture is invoked on network URL

Czytając dalej o podobnym problemie i kod sinppet, natknąłem się na jednym stanowisku, które jest wymienione poniżej:

kodu

What does SEGV_ACCERR mean?

Client fragmencie twojego Aplikacja

synchronized (mWriteFence) { 
       if (!mConnected) { 
        continue; 
       } 
       if (frame.getFrameType() == Frame.VIDEO_FRAME) { 
        writeResult = mRTMPMuxer.writeVideo(frame.getData(), frame.getOffset(), frame.getSize(), frame.getTime()); 
        calcVideoFpsAndBitrate(frame.getSize()); 

       } else if (frame.getFrameType() == Frame.AUDIO_FRAME) { 
        writeResult = mRTMPMuxer.writeAudio(frame.getData(), frame.getOffset(), frame.getSize(), frame.getTime()); 
        calcAudioBitrate(frame.getSize()); 
       } 

} 

Od wyżej kod, wydaje mi się, że jeśli aplikacja odbiera Frame.VIDEO_FRAME & Frame.AUDIO_FRAME w określonym porządku, że może być prowadzącym do jakiegoś wyścigu (może być wdrożenie modelu asynchroniczny) podczas korzystania zmienną frame wewnątrz modułu RtmpMuxerMix.writeThread.

do zawierania takich problemów:

  • powinniśmy starać się przeczytać dokumentację o bibliotece i jej najlepszych praktyk i otrzymać kod weryfikacji. Czasami pomaga odkryć oczywiste problemy w naszej logice.
  • Powinniśmy spróbować odtworzyć ten problem podczas uruchamiania aplikacji pod narzędziami dynamiki . Nie wiem o takich narzędziach na platformie Android. Nie oznacza to, że gdy zaczniemy uruchamiać aplikację w narzędziach dynamiki, sekwencja wykonania zostanie zmieniona, a potem możliwe, że będziemy w stanie odtworzyć takie problemy bardzo często lub prawie nie będziemy w stanie ich odtworzyć.

.

+0

Dzięki za odpowiedź. Zaktualizowałem kod, aby odzwierciedlić całą pętlę zapisu. Według mnie nie ma mowy, aby zmienna frame została zamieniona, podczas gdy jest używana w natywnym kodzie, ponieważ pętla while nigdy nie będzie kontynuowana dopóki nie zostanie wykonana z MUX-em. Próbowałem też wyłączyć dźwięk, aby zredukować problem do minimum, a awarie zdarzają się nadal. Przyjrzę się twoim postom. –