Щедрость
Так как это важная проблема для меня, я наложил на него награду.Я не ищу точного ответа - любой ответ заставляет меня решить эту проблему, получает награду.Пожалуйста, убедитесь, что вы видели правку чуть ниже.
Правка: с тех пор мне удалось поймать сбой в Gdb, как только он умирает (через "adb shell setprop debug.db.uid 32767") изаметил, что это та же самая проблема, которая упоминается в этом сообщении в группах Google.Показанная обратная трассировка такая же (за исключением точных адресов), как и у моего аварийного потока.Я признаю, я не мастер отладки, поэтому, если у вас есть идеи о том, что мне нужно искать, пожалуйста, дайте мне знать.
Быстрое и грязное краткое изложение
IВычеркнули большую часть кода моего достаточно большого приложения, чтобы приложение выполняло следующие действия: загружает кучу текстур через оболочки JNI (из C ++ -> Java), чтобы библиотеки Java обрабатывали декодирование для меня, делаетOpenGL текстуры из них, и очищает экран до довольно красивый, но насмешливый темно-синий цвет.Он умирает в libc, но только один раз в десять раз.
Что еще хуже, даже не похоже, что он умирает из-за какого-либо кода, который я написал - похоже, это происходит вотложенная мода, но, кажется, это не связано с чем-то таким же удобным, как сборщик мусора.В моем собственном коде нет особой точки, в которой происходит сбой - кажется, что она меняется с каждым прогоном.
Чем длиннее история
Я заканчиваю сстандартный аварийный дамп со стеком, который мне ни о чем не говорит, потому что у него есть две записи: одна в libc, а другая в том, что выглядит как недопустимый или нулевой фрейм стека.Разрешенным символом в libc является pthread_mutex_unlock.Я больше даже не использую эту функцию, поскольку избавился от необходимости многопоточности.(Собственный код вызывается в виде поверхности и просто выполняет рендеринг.)
pthread_mutex_unlock приводит к ошибке сегментации, обычно по адресу 0, но иногда с небольшим значением (менее 0x200) вместо 0. По умолчанию (и самое распространенное) мьютекс в Bionic имеет только один указатель, на который он может переключиться, и это указатель на саму структуру pthread_mutex_t.Однако более сложный мьютекс (есть несколько вариантов) может использовать дополнительные указатели.Таким образом, есть вероятность, что libc в порядке, и у libdvm есть проблема (при условии, что я могу доверять своей трассировке стека даже в такой степени).
Позвольте мне заметить, что эта проблема кажется воспроизводимой, только если я сделаю одну из этих двух вещей: отключить загрузку в части данных изображений (но все еще считывать информацию о формате / размере) и оставить буфер, который я использую для загрузки текстур в OpenGL, неинициализированным, или отключить создание текстуры OpenGL, отключив только последний вызов glTexImage2D.
Обратите внимание, что вышеупомянутый буфер для загрузки текстур в OpenGL создается только один раз и уничтожается один раз.Я попытался увеличить его и решил, что меня не беспокоит проблема переполнения буфера, специфичная для этого буфера.
Основные виновники, о которых я могу подумать:
- I 'Я не правильно использую JNI, и он делает что-то неприятное со стеком.
- У меня есть ошибка, которая повреждает кадр стека.
- Я передаю OpenGL ES что-то плохоеи он делает что-то одинаково плохо (тм).
- Мой распределенный по памяти распределитель памяти не работает должным образом.
Я прочесывал свой код для таких преступников (и не только!) несколько дней.Я не решаюсь использовать отладчик, потому что этот сбой, кажется, чувствителен к времени.Тем не менее, я все еще могу получить сбой с моим собственным собственным кодом, полностью не оптимизированным с включенными параметрами отладки.(сам gdb запускается при сканировании, как и приложение, когда оно подключено)
То, что я сделал
- Используется CheckJNI.
- Урезано докод, как я могу, пока он не перестанет падать.
- Написал обработчик сигнала и закодировал небольшую систему регистрации, чтобы выгрузить последние вещи, сделанные до того, как сигнал был брошен.
- Пытался (и не смог) обострить проблему.
- Проложенные массивы кучи с обеих сторон с канареками. Они никогда не менялись.
- Проверено 100% кода в пути кода. (Я просто не вижу проблемы.)
- Думал, что проблема волшебным образом исчезла, когда я исправил небольшую ошибку, выполнил код пятьдесят раз, чтобы убедиться, что это так, и затем на следующий день вышел из строя при первом запуске. (Ох, я никогда раньше не был так зол на ошибку!)
Вот фрагмент обычной информации о падении из 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
Использование ndk r6, платформа Android 2.2 (уровень API 8), компиляция с -Wall -Werror, только в режиме ARM.
Я смотрю на любые идеи, особенно те, которые можно проверить детерминистическим способом. Если больше информации поможет, просто оставьте комментарий (или, если вы не можете, ответ), и я обновлю свой вопрос как можно скорее. Спасибо за чтение этого далеко!
Интерфейс 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);
};
Сами определения функций начинаются с копии прототипов.
graphicsInit просто хранит размеры, которые были переданы, и немного настраивает 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<jbyteArray*>(&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<jintArray*>(&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() {
}
}
Обратите внимание на явное отсутствие безопасности потоков в коде игрового ресурса.
Дайте мне знать, будет ли полезной дополнительная информация.