Необычное и изворотливое падение Android во время загрузки кода JNI/OpenGL ES

Благодарность

Так как это важная для меня проблема, я закрепил за собой награду. Я не ищу точного ответа - любой ответ, который заставит меня решить эту проблему, получит вознаграждение. Пожалуйста, убедитесь, что вы видели правку внизу.

Правка: С тех пор мне удалось поймать крэш в Gdb, как раз когда он умирает (через "adb shell setprop debug.db.uid 32767"), и я заметил, что это та же самая проблема, которая упоминалась в этом посте на Google Groups. Показываемая обратная дорога такая же (за исключением точных адресов), как и мой аварийный поток. Признаюсь, я не мастер отладки, так что если у вас есть какие-нибудь идеи о том, что мне следует искать, пожалуйста, дайте мне знать.

Быстрая и грязная сводка

Я вырезал большую часть кода моего достаточно большого приложения, так что приложение делает следующее: Загружает кучу текстур через JNI'd обертки (от C++ --> Java) так, чтобы библиотеки Java обрабатывали декодирование для меня, делали из них OpenGL текстуры, и очищали экран до довольно красивого, но издевательского темно-синего цвета. Он умирает в libc, но только один раз в десять раз.

Хуже того, это даже не похоже на умирание, связанное ни с одним из написанных мною кодов - кажется, что это происходит с задержкой, но это не похоже на то, что это связано с чем-то настолько удобным для вины, как мусорщик. В моем собственном коде нет конкретного момента, когда происходит падение - кажется, что оно меняется в зависимости от пробега.

Более длинная история

заканчивается стандартным дампе крушения со стеком, который практически ничего не говорит мне, потому что в нем есть две записи, одна в libc, а другая в том, что выглядит как недействительный или нулевой кадр стека. Разрешенный символ в libc - pthread_mutex_unlock. Я сам больше не пользуюсь этой функцией, так как избавился от необходимости многопоточности. (Нативный код вызывается в поверхностном представлении и просто выводит.)

pthread_mutex_unlock приводит к ошибке сегментации, обычно по адресу 0, но иногда маленькое значение (менее 0x200) вместо 0. У мьютекса по умолчанию (и наиболее распространенного) в Бионике только один указатель, на который он может сегментировать, и это указатель на саму структуру pthread_mutex_t. Однако более сложный мьютекс (есть несколько вариантов) может использовать дополнительные указатели. Таким образом, шансы на то, что libc в порядке и libdvm имеет проблему (предполагая, что я могу доверять своей трассе стека даже так далеко).

Позвольте заметить, что эта проблема кажется воспроизводимой только в том случае, если я сделаю одну из этих двух вещей: отключу загрузку в порцию данных изображений (но все равно буду читать информацию о формате/размерах) и оставлю неинициализированным буфер, который я использую для загрузки текстур в OpenGL, или отключу создание текстуры OpenGL, отключив только финальный вызов glTexImage2D.

Обратите внимание, что упомянутый выше буфер для загрузки текстур в OpenGL создается только один раз и уничтожается один раз. Я попытался его увеличить и определил, что меня не беспокоит проблема переполнения буфера, характерная для этого буфера.

Основными виновниками, о которых я могу думать, являются:

  • Я не использую JNI правильно и он делает что-то неприятное со стеком.
  • У меня есть одна ошибка, которая повреждает кадр стека.
  • Я передаю OpenGL ES что-то плохое и оно делает что-то не менее плохое.
  • Мой пользовательский аллокатор памяти работает некорректно.

Я прочесывал свой код для таких преступников (и даже больше!) в течение нескольких дней. Я колеблюсь использовать отладчик, потому что этот крэш кажется чувствительным к времени. Тем не менее, я все еще могу получить крэш с моим собственным нативным кодом, полностью неоптимизированным с включенными опциями отладки. (сам gdb запускается при сканировании и приложение тоже при подключении)

Вещи, которые я делал

  • Использовал CheckJNI.
  • Снял как можно больше кода, пока он не перестанет ломаться.
  • Написал обработчик сигналов и закодировал небольшую систему протоколирования, чтобы сбрасывать последние вещи, сделанные до того, как был сброшен сигнал.
  • Пытался (и не смог) обострить проблему.
  • На обоих концах были наложены собственные массивы куч с канарейками. Они никогда не менялись.
  • Проверили 100% кода в пути кода. (Я просто не вижу проблемы.)
  • Думал, что проблема волшебным образом исчезла, когда я исправил небольшую ошибку, прогонял код пятьдесят раз, чтобы убедиться, что это так, а затем разбился на следующий день при первом прогоне. (Ooh, I've never been so angry at a bug before!)

Here's a snippet of the usual native crash info from LogCat:

I/DEBUG   ( 5818): signal 11 (SIGSEGV), fault addr 00000000
I/DEBUG   ( 5818):  r0 0000006e  r1 00000080  r2 fffffc5e  r3 100ffe58
I/DEBUG   ( 5818):  r4 00000000  r5 00000000  r6 00000000  r7 00000000
I/DEBUG   ( 5818):  r8 00000000  r9 8054f999  10 10000000  fp 0013e768
I/DEBUG   ( 5818):  ip 3b9aca00  sp 100ffe58  lr afd10640  pc 00000000  cpsr 60000010
I/DEBUG   ( 5818):  d0  643a64696f72646e  d1  6472656767756265
I/DEBUG   ( 5818):  d2  8083297880832965  d3  8083298880832973
I/DEBUG   ( 5818):  d4  8083291080832908  d5  8083292080832918
I/DEBUG   ( 5818):  d6  8083293080832928  d7  8083294880832938
I/DEBUG   ( 5818):  d8  0000000000000000  d9  0000000000000000
I/DEBUG   ( 5818):  d10 0000000000000000  d11 0000000000000000
I/DEBUG   ( 5818):  d12 0000000000000000  d13 0000000000000000
I/DEBUG   ( 5818):  d14 0000000000000000  d15 0000000000000000
I/DEBUG   ( 5818):  d16 0000000000000000  d17 3fe999999999999a
I/DEBUG   ( 5818):  d18 42eccefa43de3400  d19 3fe00000000000b4
I/DEBUG   ( 5818):  d20 4008000000000000  d21 3fd99a27ad32ddf5
I/DEBUG   ( 5818):  d22 3fd24998d6307188  d23 3fcc7288e957b53b
I/DEBUG   ( 5818):  d24 3fc74721cad6b0ed  d25 3fc39a09d078c69f
I/DEBUG   ( 5818):  d26 0000000000000000  d27 0000000000000000
I/DEBUG   ( 5818):  d28 0000000000000000  d29 0000000000000000
I/DEBUG   ( 5818):  d30 0000000000000000  d31 0000000000000000
I/DEBUG   ( 5818):  scr 80000012
I/DEBUG   ( 5818): 
I/DEBUG   ( 5818):          #00  pc 00000000  
I/DEBUG   ( 5818):          #01  pc 0001063c  /system/lib/libc.so
I/DEBUG   ( 5818): 
I/DEBUG   ( 5818): code around pc:
I/DEBUG   ( 5818): 
I/DEBUG   ( 5818): code around lr:
I/DEBUG   ( 5818): afd10620 e1a01008 e1a02007 e1a03006 e1a00005 
I/DEBUG   ( 5818): afd10630 ebfff95d e1a05000 e1a00004 ebffff46 
I/DEBUG   ( 5818): afd10640 e375006e 03a0006e 13a00000 e8bd81f0 
I/DEBUG   ( 5818): afd10650 e304cdd3 e3043240 e92d4010 e341c062 
I/DEBUG   ( 5818): afd10660 e1a0e002 e24dd008 e340300f e1a0200d 
I/DEBUG   ( 5818): 
I/DEBUG   ( 5818): stack:
I/DEBUG   ( 5818):     100ffe18  00000000  
I/DEBUG   ( 5818):     100ffe1c  00000000  
I/DEBUG   ( 5818):     100ffe20  00000000  
I/DEBUG   ( 5818):     100ffe24  ffffff92  
I/DEBUG   ( 5818):     100ffe28  100ffe58  
I/DEBUG   ( 5818):     100ffe2c  00000000  
I/DEBUG   ( 5818):     100ffe30  00000080  
I/DEBUG   ( 5818):     100ffe34  8054f999  /system/lib/libdvm.so
I/DEBUG   ( 5818):     100ffe38  10000000  
I/DEBUG   ( 5818):     100ffe3c  afd10640  /system/lib/libc.so
I/DEBUG   ( 5818):     100ffe40  00000000  
I/DEBUG   ( 5818):     100ffe44  00000000  
I/DEBUG   ( 5818):     100ffe48  00000000  
I/DEBUG   ( 5818):     100ffe4c  00000000  
I/DEBUG   ( 5818):     100ffe50  e3a07077  
I/DEBUG   ( 5818):     100ffe54  ef900077  
I/DEBUG   ( 5818): #01 100ffe58  00000000  
I/DEBUG   ( 5818):     100ffe5c  00000000  
I/DEBUG   ( 5818):     100ffe60  00000000  
I/DEBUG   ( 5818):     100ffe64  00000000  
I/DEBUG   ( 5818):     100ffe68  00000000  
I/DEBUG   ( 5818):     100ffe6c  00000000  
I/DEBUG   ( 5818):     100ffe70  00000000  
I/DEBUG   ( 5818):     100ffe74  00000000  
I/DEBUG   ( 5818):     100ffe78  00000000  
I/DEBUG   ( 5818):     100ffe7c  00000000  
I/DEBUG   ( 5818):     100ffe80  00000000  
I/DEBUG   ( 5818):     100ffe84  00000000  
I/DEBUG   ( 5818):     100ffe88  00000000  
I/DEBUG   ( 5818):     100ffe8c  00000000  
I/DEBUG   ( 5818):     100ffe90  00000000  
I/DEBUG   ( 5818):     100ffe94  00000000  
I/DEBUG   ( 5818):     100ffe98  00000000  
I/DEBUG   ( 5818):     100ffe9c  00000000  

Using ndk r6, Android platform 2.2 (API level 8), компиляция только с -Wall -Werror, ARM mode.

Я смотрю на любые идеи, особенно на те, которые поддаются проверке детерминированным способом. Если больше информации поможет, просто оставьте комментарий (или, если нет, ответ) и я как можно скорее обновлю свой вопрос ASAP. Спасибо за чтение!

Интерфейс JNI

Есть и j2n и n2j вызовы. Единственные вызовы j2n на данный момент находятся здесь:

private static class Renderer implements GLSurfaceView.Renderer {
    public void onDrawFrame(GL10 gl) {
        GraphicsLib.graphicsStep();
    }

    public void onSurfaceChanged(GL10 gl, int width, int height) {
        GraphicsLib.graphicsInit(width, height);
    }

    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        // Do nothing.
    }
}

Этот код проходит через этот интерфейс:

public class GraphicsLib {

     static {
         System.loadLibrary("graphicslib");
     }

     public static native void graphicsInit(int width, int height);
     public static native void graphicsStep();
}

Который на родной стороне выглядит как:

extern "C" {
    JNIEXPORT void JNICALL FN(graphicsInit)(JNIEnv* env, jobject obj,  jint width, jint height);
    JNIEXPORT void JNICALL FN(graphicsStep)(JNIEnv* env, jobject obj);
};

Сами определения функций начинаются с копии прототипов.

ГрафикаПросто запоминает переданные размеры и немного настраивает OpenGL без ничего особенно интересного. graphicsStep очищает экран до приятного цвета и вызывает LoadSprites(env).

Более сложная сторона состоит из вызовов n2j, используемых в LoadSprites(), которые загружают в спрайт каждый кадр. Не элегантное решение, но оно работает, за исключением этого сбоя.

LoadSprites работает следующим образом:

GameAssetsInfo gai;
void LoadSprites(JNIEnv* env)
{
    InitGameAssets(gai, env);
    CatchJNIException(env, "j0");
    ...
    static int z = 0;
    if (z < numSprites)
    {
        CatchJNIException(env, "j1");
        OpenGameImage(gai, SpriteIDFromNumber(z));
        CatchJNIException(env, "j2");
        unsigned int actualWidth = GetGameImageWidth(gai);
        CatchJNIException(env, "j3");
        unsigned int actualHeight = GetGameImageHeight(gai);
        CatchJNIException(env, "j4");
        ...
        jint i;
        int r = 0;
        CatchJNIException(env, "j5");
        do {
            CatchJNIException(env, "j6");
            i = ReadGameImage(gai);
            CatchJNIException(env, "j7");
            if (i > 0)
            {
                // Deal with the pure data chunk -- One line at a time.
                CatchJNIException(env, "j8");
                StoreGameImageChunk(gai, (int*)sprites[z].data + r, 0, i);
                ...
                r += sprites[z].width;
                CatchJNIException(env, "j9");
                UnreadGameImage(gai);
                CatchJNIException(env, "j10");
            } else {
                break;
            }
        } while (true);

        CatchJNIException(env, "j11");
        CloseGameImage(gai);
        CatchJNIException(env, "j12");

        ... OpenGL ES calls ...

        glTexImage2D( ... );

        z++;
    }

    CatchJNIException(env, "j13");
}

Где CatchJNIException это (и никогда ничего не печатает для меня):

void CatchJNIException(JNIEnv* env, const char* str)
{
    jthrowable exc = env->ExceptionOccurred();
    if (exc) {
        jclass newExcCls;
        env->ExceptionDescribe();
        env->ExceptionClear();
        newExcCls = env->FindClass( 
            "java/lang/IllegalArgumentException");
        if (newExcCls == NULL) {
            // Couldn't find the exception class.. Uuh..
            LOGE("Failed to catch JNI exception entirely -- could not find exception class.");
            return;
            abort();
        }
        LOGE("Caught JNI exception. (%s)", str);
        env->ThrowNew( newExcCls, "thrown from C code");
//      abort();
    }
}

И соответствующая часть GameAssetInfo и связанного с ней кода вызывается только из родного кода и работает следующим образом:

void InitGameAssets(GameAssetsInfo& gameasset, JNIEnv* env)
{
    CatchJNIException(env, "jS0");
    FST;
    char str[64];
    sprintf(str, "%s/GameAssets", ROOTSTR);

    gameasset.env = env;
    CatchJNIException(gameasset.env, "jS1");
    gameasset.cls = gameasset.env->FindClass(str);
    CatchJNIException(gameasset.env, "jS2");
    gameasset.openAsset = gameasset.env->GetStaticMethodID(gameasset.cls, "OpenAsset", "(I)V");
    CatchJNIException(gameasset.env, "jS3");
    gameasset.readAsset = gameasset.env->GetStaticMethodID(gameasset.cls, "ReadAsset", "()I");
    CatchJNIException(gameasset.env, "jS4");
    gameasset.closeAsset = gameasset.env->GetStaticMethodID(gameasset.cls, "CloseAsset", "()V");
    CatchJNIException(gameasset.env, "jS5");
    gameasset.buffID = gameasset.env->GetStaticFieldID(gameasset.cls, "buff", "[B");

    CatchJNIException(gameasset.env, "jS6");
    gameasset.openImage = gameasset.env->GetStaticMethodID(gameasset.cls, "OpenImage", "(I)V");
    CatchJNIException(gameasset.env, "jS7");
    gameasset.readImage = gameasset.env->GetStaticMethodID(gameasset.cls, "ReadImage", "()I");
    CatchJNIException(gameasset.env, "jS8");
    gameasset.closeImage = gameasset.env->GetStaticMethodID(gameasset.cls, "CloseImage", "()V");
    CatchJNIException(gameasset.env, "jS9");
    gameasset.buffIntID = gameasset.env->GetStaticFieldID(gameasset.cls, "buffInt", "[I");
    CatchJNIException(gameasset.env, "jS10");
    gameasset.imageWidth = gameasset.env->GetStaticFieldID(gameasset.cls, "imageWidth", "I");
    CatchJNIException(gameasset.env, "jS11");
    gameasset.imageHeight = gameasset.env->GetStaticFieldID(gameasset.cls, "imageHeight", "I");
    CatchJNIException(gameasset.env, "jS12");
    gameasset.imageHasAlpha = gameasset.env->GetStaticFieldID(gameasset.cls, "imageHasAlpha", "I");
    CatchJNIException(gameasset.env, "jS13");
}

void OpenGameAsset(GameAssetsInfo& gameasset, int rsc)
{
    FST;
    CatchJNIException(gameasset.env, "jS14");
    gameasset.env->CallStaticVoidMethod(gameasset.cls, gameasset.openAsset, rsc);
    CatchJNIException(gameasset.env, "jS15");
}

void CloseGameAsset(GameAssetsInfo& gameasset)
{
    FST;
    CatchJNIException(gameasset.env, "jS16");
    gameasset.env->CallStaticVoidMethod(gameasset.cls, gameasset.closeAsset);
    CatchJNIException(gameasset.env, "jS17");
}

int ReadGameAsset(GameAssetsInfo& gameasset)
{
    FST;
    CatchJNIException(gameasset.env, "jS18");
    int ret = gameasset.env->CallStaticIntMethod(gameasset.cls, gameasset.readAsset);
    CatchJNIException(gameasset.env, "jS19");
    if (ret > 0)
    {
    CatchJNIException(gameasset.env, "jS20");
        gameasset.obj = gameasset.env->GetStaticObjectField(gameasset.cls, gameasset.buffID);
    CatchJNIException(gameasset.env, "jS21");
        gameasset.arr = reinterpret_cast(&gameasset.obj);
    }
    return ret;
}

void UnreadGameAsset(GameAssetsInfo& gameasset)
{
    FST;
    CatchJNIException(gameasset.env, "jS22");
    gameasset.env->DeleteLocalRef(gameasset.obj);
    CatchJNIException(gameasset.env, "jS23");
}

void StoreGameAssetChunk(GameAssetsInfo& gameasset, void* store, int offset, int length)
{
    FST;
    CatchJNIException(gameasset.env, "jS24");
    gameasset.env->GetByteArrayRegion(*gameasset.arr, offset, length, (jbyte*)store);
    CatchJNIException(gameasset.env, "jS25");
}

void OpenGameImage(GameAssetsInfo& gameasset, int rsc)
{
    FST;
    CatchJNIException(gameasset.env, "jS26");
    gameasset.env->CallStaticVoidMethod(gameasset.cls, gameasset.openImage, rsc);
    CatchJNIException(gameasset.env, "jS27");
    gameasset.l_imageWidth = (int)gameasset.env->GetStaticIntField(gameasset.cls, gameasset.imageWidth);
    CatchJNIException(gameasset.env, "jS28");
    gameasset.l_imageHeight = (int)gameasset.env->GetStaticIntField(gameasset.cls, gameasset.imageHeight);
    CatchJNIException(gameasset.env, "jS29");
    gameasset.l_imageHasAlpha = (int)gameasset.env->GetStaticIntField(gameasset.cls, gameasset.imageHasAlpha);
    CatchJNIException(gameasset.env, "jS30");
}

void CloseGameImage(GameAssetsInfo& gameasset)
{
    FST;
    CatchJNIException(gameasset.env, "jS31");
    gameasset.env->CallStaticVoidMethod(gameasset.cls, gameasset.closeImage);
    CatchJNIException(gameasset.env, "jS32");
}

int ReadGameImage(GameAssetsInfo& gameasset)
{
    FST;
    CatchJNIException(gameasset.env, "jS33");
    int ret = gameasset.env->CallStaticIntMethod(gameasset.cls, gameasset.readImage);
    CatchJNIException(gameasset.env, "jS34");
    if ( ret > 0 )
    {
        CatchJNIException(gameasset.env, "jS35");
        gameasset.obj = gameasset.env->GetStaticObjectField(gameasset.cls, gameasset.buffIntID);
        CatchJNIException(gameasset.env, "jS36");
        gameasset.arrInt = reinterpret_cast(&gameasset.obj);
    }
    return ret;
}

void UnreadGameImage(GameAssetsInfo& gameasset)
{
    FST;
    CatchJNIException(gameasset.env, "jS37");
    gameasset.env->DeleteLocalRef(gameasset.obj);
    CatchJNIException(gameasset.env, "jS38");
}

void StoreGameImageChunk(GameAssetsInfo& gameasset, void* store, int offset, int length)
{
    FST;
    CatchJNIException(gameasset.env, "jS39");
    gameasset.env->GetIntArrayRegion(*gameasset.arrInt, offset, length, (jint*)store);
    CatchJNIException(gameasset.env, "jS40");
}

int GetGameImageWidth(GameAssetsInfo& gameasset) { return gameasset.l_imageWidth; }
int GetGameImageHeight(GameAssetsInfo& gameasset) { return gameasset.l_imageHeight; }
int GetGameImageHasAlpha(GameAssetsInfo& gameasset) { return gameasset.l_imageHasAlpha; }

И это поддерживается на Java-стороне:

public class GameAssets {
    static public Resources res = null;
    static public InputStream is = null;
    static public byte buff[];
    static public int buffInt[];
    static public final int buffSize = 1024;
    static public final int buffIntSize = 2048;

    static public int imageWidth;
    static public int imageHeight;
    static public int imageHasAlpha;
    static public int imageLocX;
    static public int imageLocY;
    static public Bitmap mBitmap;
    static public BitmapFactory.Options decodeResourceOptions = new BitmapFactory.Options();

    public GameAssets(Resources r) {
        res = r;
        buff = new byte[buffSize];
        buffInt = new int[buffIntSize];
        decodeResourceOptions.inScaled = false;
    }
    public static final void OpenAsset(int id) {
        is = res.openRawResource(id);
    }
    public static final int ReadAsset() {
        int num = 0;
        try {
            num = is.read(buff);
        } catch (Exception e) {
            ;
        }
        return num;
    }
    public static final void CloseAsset() {
        try {
            is.close();
        } catch (Exception e) {
            ;
        }
        is = null;
    }

    // We want all the advantages that BitmapFactory can provide -- reading
    // images of compressed image formats -- so we provide our own interface
    // for it.
    public static final void OpenImage(int id) {
        mBitmap = BitmapFactory.decodeResource(res, id, decodeResourceOptions);
        imageWidth = mBitmap.getWidth();
        imageHeight = mBitmap.getHeight();
        imageHasAlpha = mBitmap.hasAlpha() ? 1 : 0;
        imageLocX = 0;
        imageLocY = 0;
    }
    public static final int ReadImage() {
        if (imageLocY >= imageHeight) return 0;
        int numReadPixels = buffIntSize;
        if (imageLocX + buffIntSize >= imageWidth)
        {
            numReadPixels = imageWidth - imageLocX;
            mBitmap.getPixels(buffInt, 0, imageWidth, imageLocX, imageLocY, numReadPixels, 1);
            imageLocY++;
        }
        else
        {
            mBitmap.getPixels(buffInt, 0, imageWidth, imageLocX, imageLocY, numReadPixels, 1);
            imageLocX += numReadPixels;
        }
        return numReadPixels;
    }
    public static final void CloseImage() {
    }
}

Пожалуйста, обратите внимание на явное отсутствие безопасности потока в коде игрового актива.

Дайте мне знать, если будет полезной дополнительная информация.

5
задан Kaganar 8 August 2011 в 17:08
поделиться