【问题标题】:How to debug SEGV_ACCERR如何调试 SEGV_ACCERR
【发布时间】:2017-06-09 01:57:43
【问题描述】:

我有一个使用KickflipButterflyTV 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/system@framework@boot.oat (offset 0x1ed6000)

同样,在 2 小时的直播中,这可能永远不会发生,也可能会在直播 10 分钟后发生。调试起来非常困难,因为我不能强迫错误发生。

有什么办法可以改善我得到的调试信息? SEGV_ACCER 到底是什么意思?我读到这“意味着您试图访问您无权访问的地址。”但我不确定这意味着什么,因为我可以连续播放数小时而不会发生错误。

有什么方法可以捕捉到信号并继续?

编辑:添加更多信息,这是应用程序崩溃的本机库的一部分(使用 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] in 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();
                       }
                   }
               }
           }

       }

   }

请注意,即使我根本不发送音频也会发生崩溃。

【问题讨论】:

  • 我猜你超出了你的阵列。你确定你传递了正确数量的数组元素吗? length 是否与从 GetArrayLength() 返回的元素数匹配? data 的实际值是多少?您能否修改您的 JNI 代码以某种方式记录或以其他方式显示该信息?
  • @AndrewHenle 所有这一切都很难弄清楚,因为这种情况每秒发生 30 次,而且崩溃只是随机发生(而且可能需要几个小时才能发生),我可以尝试打印这些信息为每一帧找出崩溃发生时的值,但是代码执行的次数更少(由于日志记录会减慢它的速度),因此实际发生可能需要几个小时......无论如何数据永远不会少于 3 bytes 是在敏感点被访问的索引。
  • 所有这些都很难弄清楚... 是的,很难弄清楚内存损坏问题。最简单的解决方案可能是为您的 JNI 库编写一个简单的 C 驱动程序,并从一个简单的 C 程序调用您的 rtmp_sender_write_video_frame() 函数。然后,您可以轻松地将代码移植到 Linux 并在 Valgrind 下运行。请注意,即使这样做也可能找不到您的内存损坏错误 - JNI 非常无情,任何地方的内存损坏都可能在看似完全不相关的代码中产生故障。
  • @AndrewHenle 你能检查我对另一个答案的评论吗?当我的缓冲区“离开”java内存空间时,hte GC是否有可能收集它们?
  • hte GC 是否有可能在我的缓冲区“离开”java 内存空间时收集它们? 我从未见过,这将是一个巨大的错误对于 JVM 来说,GC 是一个传递给 JNI 函数的数组。你是唯一一个观察到这种错误的人的几率非常小。

标签: android android-ndk java-native-interface


【解决方案1】:

"您可以将数据存储在byte[] 中。这样可以非常快速地从 托管代码。但是,在本机方面,您不能保证 无需复制即可访问数据。”

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

分析

一些思考和尝试:

  • 它失败的代码非常通用,所以可能没有错误
  • 一定是frame数据已被删除/损坏/锁定/移动
  • Java 垃圾收集器是否已删除或重新定位数据?
  • 您可以将详细的调试信息写入一个文件,在每个文件中覆盖它 框架,因此您只有一个包含最后调试信息的小日志。
  • 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 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
    }
    //...

然后是一个新方法:

    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:所以我们不想将我们有限的资源用于 支持它。

see: 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

【讨论】:

  • 感谢您的回答。 java和JNI之间的内存管理是如何工作的?如果 JNI 中仍然存在引用,缓冲区可以被 GC 吗?我已经在 Kickflip 中阅读了有关 RTMP 的那些问题,这就是为什么我从前没有使用他们(有缺陷的)RTMP 实现并为此切换到轻量级的 ButterflyTV。
  • 我当然希望 JVM 不会在调用仍在运行时对传递给 JNI 调用的数组进行 GC。我同意尽管发布的代码中可能没有任何内存损坏 - 发布的代码可能只是受害者,并且失败发生在发布的代码中,因为它经常被调用并且会产生很多调用依赖于动态内存。换句话说,它是最有可能在由其他一些代码埋下的地雷上运行的代码。
  • @JonGoodwin 我需要做更多的测试,但看起来这可能已经解决了这个问题。不过我持怀疑态度,感觉很奇怪,仅使用字节数组很容易导致内存被重用/收集......如果一切都是肯定的,我会做更多的测试并标记答案(有赏金):)跨度>
  • 作为记录,我使用的 ByteBuffers 直接来自 MediaCodec api 作为编码帧,因此本机代码接收 DirectByteBuffer 而不必提供字节数组确实更有意义,所以无论如何,我认为这样的代码更干净。
  • 酷。它的正常工作方式是将传递给 JNI 的指针存储在一个特殊的表中,如果数据被 GC 重定位,则该表指向数据的指针会更改,但指向 JNI 的指针地址保持不变,因此 JNI 看不到数据移动,并成功引用数据。当你在 JNI 线程中时,可能会发生 GC。 JVM 决定是否将原始(希望是间接的)指针或数据副本传递给 JNI。是的,我也觉得你的 SEEM 有一个绝对原始指针很奇怪,然后它会受到 GC 的影响。
【解决方案2】:

根据问题的症状/描述,您的程序很可能遇到某种无效的内存访问/损坏 这在某种程度上与多线程竞争条件场景有关。根据我过去的经验,调试内存损坏本身 非常困难,如果它与多线程环境相关联,就变得非常非常困难。我之前的一些帖子可能 会有所帮助,并就这些主题提供一些一般性指导。请注意,这些帖子与 Windows/Linux 和 不适用于安卓平台。

cpp - valgrind - Invalid read of size 8

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

在进一步阅读类似问题和您的代码 sinppet 时,我遇到了一篇文章,如下所述:

What does SEGV_ACCERR mean?

您的应用程序的客户端代码 sn-p

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 &amp; Frame.AUDIO_FRAME,则在我看来,在使用frame 中的RtmpMuxerMix.writeThread 变量时可能会导致某种竞争条件(可能是异步模型实现)模块。

总结此类问题:

  • 我们应该尝试阅读有关库及其 最佳实践并审查您的代码。有时它有助于找出我们逻辑中的明显问题。
  • 我们应该在运行应用程序时尝试重现此问题 动力学工具。我不知道 Android 平台上的此类工具。请注意,一旦我们开始在动态工具下运行应用程序,执行顺序就会发生变化,之后我们可能会非常频繁地重现此类问题,或者几乎无法重现。

【讨论】:

  • 感谢您的回答。我更新了代码以反映整个写入循环。据我了解,在本机代码中使用帧变量时,它不可能被替换,因为 while 循环将永远不会继续,直到它使用多路复用器完成。此外,我尝试禁用音频以将问题减少到最低限度,并且崩溃仍然发生。我会看看你的帖子。
猜你喜欢
  • 2012-10-05
  • 1970-01-01
  • 1970-01-01
  • 2012-07-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-02-13
  • 2012-11-27
相关资源
最近更新 更多