Как отладить 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. Пожалуйста, не забудьте, что как только мы начнем запускать приложение в динамических инструментах, последовательность выполнения будет изменена, и после этого возможно, что мы сможем воспроизвести такие проблемы очень часто или почти не сможете воспроизвести их.
.