Как отладить SEGV_ACCERR

У меня есть приложение, которое передает видео с помощью Kickflip и ButterflyTV libRTMP

Теперь в 99% случаев приложение работает нормально, но время от времени я получаю ошибку сегментации, которую я не могу отлаживать, поскольку сообщения слишком загадочны:

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)

Опять же, через 2 часа это может не произойти, или это может произойти через 10 минут в потоке. Это очень сложно отлаживать, потому что я не могу заставить ошибку произойти.

Есть ли способ улучшить информацию отладки, которую я получаю? Что означает SEGV_ACCER? Я читал, что это означает, что вы пытались получить доступ к адресу, к которому у вас нет доступа к доступу ". но я не уверен, что это значит, поскольку я могу работать в течение нескольких часов без ошибок.

Есть ли способ поймать сигнал и продолжить?

EDIT: чтобы добавить дополнительную информацию, это часть родной библиотеки, где приложение сбой (найдено с помощью 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;
}

Сбой происходит при buf[zeros_in_startcode] в find_start_code. Я также удалил несколько строк android_log (не думаю, что это имеет значение?).

Насколько я понимаю, этот буфер должен быть доступен, не имеет смысла, что он падает только "иногда".

PS. вот где я вызываю собственный код из Java:

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();
                       }
                   }
               }
           }

       }

   }

Обратите внимание, что авария происходит даже тогда, когда я вообще не отправляю аудио.

Ответ 1

"Вы можете сохранить данные в byte[]. Это позволяет очень быстро получить доступ из управляемый код. Однако на родной стороне вам не гарантировано быть возможность доступа к данным без необходимости их копирования".

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

Анализ

Некоторые размышления и вещи, которые нужно попробовать:

  • Код, где он падает, очень общий, поэтому, вероятно, нет ошибок там
  • Должно быть, данные frame удалены/повреждены/заблокированы/перемещены
  • Удалил ли сборщик мусора Java OR переместил данные?
  • Вы можете написать подробный отладочный файл, перезаписывая его на каждом , поэтому у вас есть только небольшой журнал с последней информацией об отладке.
  • отправьте локальную копию информации переменной frame (с помощью ByteBuffer) на mRTMPMuxer.writeVideo
    В отличие от обычных буферов byte, в ByteBuffer хранилище не выделяется на управляемом heap и может быть доступно всегда непосредственно из собственного кода.

Реализация

//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;
}

Отладка

Некоторый код из SO Catching исключений, отбрасываемых из собственного кода:

    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
    }
    //...

Затем новый метод:

    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);
    }
}

Не зацикливайтесь на SEGV_ACCERR, у вас есть ошибка сегментации, SIGSEGV (вызванная программой, пытающейся читать или записывать нелегальную ячейку памяти, читать в вашем случае).
От siginfo.h:

SEGV_MAPERR означает, что вы пытались получить доступ к адресу, который не сопоставляется ни с чем. SEGV_ACCERR означает, что вы пытались получить доступ к адресу, к которому у вас нет доступа.

Другие

Это может представлять интерес:

В: Я заметил, что была поддержка RTMP. Но патч, который удаляет RTMP был объединен.
Q: Не могли бы вы рассказать мне, почему?
A: Мы не делаем считают, что RTMP обслуживает случай использования мобильного вещания, а также HLS,
A: и поэтому мы не хотим посвящать наши ограниченные ресурсы поддерживая его.

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

Я предлагаю вам зарегистрировать проблему с:
https://github.com/Kickflip/kickflip-android-sdk/issues
https://github.com/ButterflyTV/LibRtmp-Client-for-Android/issues

Ответ 2

По признаку/описанию проблемы, ваша программа, скорее всего, испытывает какой-то недопустимый доступ к памяти/коррупцию что как-то связано с многопоточным сценарием условий гонки. Из моего прошлого опыта отладка самой коррупции памяти очень сложно, и если это связано с многопоточной средой, становится очень сложно. Некоторые из моих предыдущих сообщений могли быть полезными и предоставлять некоторые общие рекомендации по этим темам. Обратите внимание, что эти сообщения относятся к Windows/Linux и а не для платформы Android.

cpp - valgrind - Неверное чтение размера 8

Иногда возникает ошибка сегментации, когда функция cvCreateFileCapture вызывается по сетевому URL

В то время, когда мы читаем далее о подобной проблеме и вашем кодексе sinppet, я наткнулся на один пост, который упоминается ниже:

Что означает SEGV_ACCERR?

фрагмент кода клиента вашего приложения

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());
                }

}

Из вышеприведенного кода мне кажется, что если ваше приложение получает Frame.VIDEO_FRAME & Frame.AUDIO_FRAME в определенном порядке, это может привести к некоторому состоянию гонки (может быть, реализация асинхронной модели) при использовании переменной frame в RtmpMuxerMix.writeThread модуль.

Чтобы завершить такие вопросы:

  • мы должны попытаться прочитать документацию о библиотеке и ее передовой практики и проверки вашего кода. Иногда это помогает выявить очевидные проблемы в нашей логике.
  • Мы должны попытаться воспроизвести эту проблему во время работы приложения под инструменты динамики. Я не знаю о таких инструментах на платформе Android. Пожалуйста, не забудьте, что как только мы начнем запускать приложение в динамических инструментах, последовательность выполнения будет изменена, и после этого возможно, что мы сможем воспроизвести такие проблемы очень часто или почти не сможете воспроизвести их.

.