Join FlipAndroid.COM Telegram Group: https://t.me/joinchat/F_aqThGkhwcLzmI49vKAiw


Cómo depurar SEGV_ACCERR

Tengo una aplicación que transmite vídeo usando Kickflip y ButterflyTV libRTMP

Ahora, el 99% del tiempo que la aplicación está funcionando bien, pero de vez en cuando me sale una falla de segmentación nativa que no soy capaz de depurar, ya que los mensajes son demasiado crípticos:

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/system@framework@boot.oat (offset 0x1ed6000) 

Una vez más, en un flujo de 2 horas esto puede no suceder nunca o puede suceder 10 minutos en el arroyo. Es muy difícil de depurar porque no puedo forzar el error a suceder.

¿Hay alguna forma de mejorar la información de depuración que recibo? ¿Qué significa exactamente SEGV_ACCER? He leído que esto "significa que intentó acceder a una dirección a la que no tiene permiso de acceso." Pero no estoy seguro de lo que eso significa, ya que puedo transmitir durante horas sin que ocurra el error.

¿Hay alguna manera de captar la señal y simplemente continuar?

EDIT: para agregar más información, esta es la parte de la biblioteca nativa en la que falla la aplicación (encontrada usando 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] ocurre en buf[zeros_in_startcode] en find_start_code . He quitado algunas líneas de android_log también (no creo que esto importa?).

A mi entender, este tampón debe ser accesible, no tiene sentido que se bloquea sólo "a veces".

PD. Aquí es donde llamo el código nativo de 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(); } } } } } } 

Tenga en cuenta que el accidente ocurre incluso cuando no envíe audio en absoluto.

  • Android Camera takePicture utiliza Previews small buffer
  • Android - javah no encuentra mi clase
  • Gradle de Android, libs nativas para diferentes arquitecturas
  • Comprimir vídeos utilizando FFMPEG y JNI
  • Copiar un buffer de bytes con JNI
  • Javah Error android.app.Activity not found
  • Android NDK R8E falta stdlib.h
  • Cómo incluir bibliotecas compartidas precompiladas en apk con eclipse
  • 2 Solutions collect form web for “Cómo depurar SEGV_ACCERR”

    "Puede almacenar los datos en un byte [], lo que permite un acceso muy rápido desde el código administrado. Sin embargo, en el lado nativo, no se garantiza la posibilidad de acceder a los datos sin tener que copiarlos". Consulte https://developer.android.com/training/articles/perf-jni.html
    Algunas reflexiones y cosas para probar:

    • El código en el que se cae es muy genérico, por lo que probablemente ningún error allí
    • Debe ser el frame datos se ha eliminado / dañado / bloqueado / movido
    • ¿Ha eliminado el recolector de basura de Java o ha vuelto a localizar los datos?
    • Puede escribir depuración detallada en un archivo, sobrescribiéndolo en cada trama, por lo que sólo tiene un pequeño registro con la última información de depuración.
    • Envíe una copia local de la variable variable de frame (utilizando ByteBuffer ) a mRTMPMuxer.writeVideo
      A diferencia de los búferes de byte normales, en ByteBuffer el almacenamiento no está asignado en el heap administrado y siempre se puede acceder directamente desde el código nativo.
       //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; } 

    Algún código de excepciones capturar desde el código nativo :

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

    Luego un nuevo método:

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

    Esto puede ser de interés:

    P: Me di cuenta de que había soporte de RTMP. Pero un parche que eliminaba RTMP se había fusionado.
    P: ¿Podría decirme por qué?
    R: No creemos que RTMP sirva al caso de uso de radiodifusión móvil, así como a HLS,
    R: Por lo tanto, no queremos dedicar nuestros recursos limitados a apoyarlo.

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

    No se colgó demasiado en SEGV_ACCERR , tiene un fallo de segmentación, SIGSEGV (causado por un programa que intenta leer o escribir una ubicación de memoria ilegal, leer en su caso).
    De siginfo.h:

    SEGV_MAPERR significa que intentó acceder a una dirección que no se correlaciona con nada. SEGV_ACCERR significa que intentó acceder a una dirección a la que no tiene permiso de acceso.

    Le sugiero que registre un problema con:
    https://github.com/Kickflip/kickflip-android-sdk/issues
    https://github.com/ButterflyTV/LibRtmp-Client-for-Android/issues

    Por síntoma / descripción del problema, es probable que su programa experimente algún tipo de acceso / corrupción de memoria no válida que esté relacionada de alguna manera con el escenario de condición de carrera multi-hilo. De mi experiencia pasada, la corrupción de memoria de depuración en sí es muy difícil y si está vinculado al entorno multi-hilo se vuelve muy, muy difícil. Algunas de mis publicaciones anteriores podrían ser útiles y proporcionar algunas pautas generales sobre estos temas. Tenga en cuenta que estas publicaciones están relacionadas con Windows / Linux y no con la plataforma de Android.

    Cpp – valgrind – Lectura no válida de tamaño 8

    Un error de segmentación a veces se produce cuando la función cvCreateFileCapture se invoca en la URL de red

    Mientras leía más sobre un problema similar y su código sinppet, me encontré con un mensaje que se menciona a continuación:

    ¿Qué significa SEGV_ACCERR?

    Snippet de código de cliente de su aplicación

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

    Desde el código anterior, me parece que si su aplicación recibe Frame.VIDEO_FRAME & Frame.AUDIO_FRAME en cierto orden podría estar llevando a algún tipo de condición de carrera (puede ser la implementación del modelo asíncrono) mientras se utiliza la variable frame dentro del módulo RtmpMuxerMix.writeThread .

    Para concluir estas cuestiones:

    • Debemos tratar de leer la documentación sobre la biblioteca y sus mejores prácticas y obtener su código revisado. En algún momento ayuda a descubrir problemas obvios en nuestra lógica.
    • Debemos intentar reproducir este problema mientras se ejecuta la aplicación bajo herramientas de dinámica. No soy consciente de tales herramientas en la plataforma Android. Por favor, no que una vez que comenzamos a ejecutar la aplicación bajo herramientas de dinámica, la secuencia de ejecución se cambia y después de eso es posible que sea sea capaz de reproducir tales problemas con mucha frecuencia o casi no ser capaz de reproducirlo.

    .

    FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.