Так как это важная для меня проблема, я закрепил за собой награду. Я не ищу точного ответа - любой ответ, который заставит меня решить эту проблему, получит вознаграждение. Пожалуйста, убедитесь, что вы видели правку внизу.
Правка: С тех пор мне удалось поймать крэш в 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 создается только один раз и уничтожается один раз. Я попытался его увеличить и определил, что меня не беспокоит проблема переполнения буфера, характерная для этого буфера.
Основными виновниками, о которых я могу думать, являются:
Я прочесывал свой код для таких преступников (и даже больше!) в течение нескольких дней. Я колеблюсь использовать отладчик, потому что этот крэш кажется чувствительным к времени. Тем не менее, я все еще могу получить крэш с моим собственным нативным кодом, полностью неоптимизированным с включенными опциями отладки. (сам gdb запускается при сканировании и приложение тоже при подключении)
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. Спасибо за чтение!
Есть и 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() {
}
}
Пожалуйста, обратите внимание на явное отсутствие безопасности потока в коде игрового актива.
Дайте мне знать, если будет полезной дополнительная информация.