앱이 어떤 기기에서만 죽었고 언제 죽는지도 일정하지 않았다.
Table of contents
Open Table of contents
주소만 나왔다
죽을 때 logcat 을 봤는데 신호 번호와 주소만 있었다.
Fatal signal 11 (SIGSEGV)
#00 pc 0001a2b4 libgame.so
자바에서 죽으면 어느 줄인지 나오는데 네이티브 쪽은 주소뿐이다. 어디서 죽었는지를 알 방법이 로그에 없었다.
디버그 정보를 따로 보관했다
찾아보니 ndk-stack 이 있었다. logcat 을 그대로 물려 주면 주소를 함수와 줄 번호로 바꿔 준다.
$ adb logcat | ndk-stack -sym obj/local/armeabi
-sym 이 가리키는 것은 심볼이 안 지워진 쪽이다. 앱에 담기는 libs/armeabi/ 는 지워져 있고 obj/local/armeabi/ 에 원본이 남는다. 그런데 그 폴더는 다음 빌드가 덮는다. 그래서 배포한 판마다 그것을 따로 보관하게 했다. 같은 빌드여야 주소가 맞으므로 어느 판에서 죽었는지를 알아야 맞는 것을 꺼낸다.
예외를 안 보고 있었다
이렇게 해서 죽는 자리를 찾으니 자바를 부르는 부분이었다. 부르고 나서 ExceptionCheck() 를 안 하고 있었다.
자바 쪽에서 예외가 나면 그 상태가 JNIEnv 에 남는다. 그 상태로 다음 호출을 하면 죽는다. 확인하고 ExceptionClear() 로 지우는 것을 넣었다. 확인만 하고 안 지우면 여전히 남아 다음 호출에서 또 죽는다.
왜 예외가 났는지도 봤다. 전달한 값이 유효 범위를 벗어났는데 기기마다 화면 크기나 설정이 달라 어떤 기기에서만 범위 밖이 됐다. 넘기기 전에 값을 검사하는 것도 넣었다.
두 곳에서 막은 셈이다. 값 검사만 하면 다른 이유로 나는 예외를 못 잡는다. 예외 확인만 하면 예외가 나긴 난다. 둘 다 필요했다.
참조의 짝
같은 부분에서 다른 것도 나왔다. 자바 객체를 나중에 쓰려고 오래 들고 있었는데 넘어온 참조는 그 호출 동안만 유효하다.
jobject global = env->NewGlobalRef(obj);
// 다 쓰면
env->DeleteGlobalRef(global);
오래 들고 있으려면 NewGlobalRef 로 바꾸고 다 쓰면 DeleteGlobalRef 로 놓아야 한다. 여기서도 얻으면 놓는 짝이었다. 경계를 넘는 것은 대개 짝이 있었다.
정리
- 네이티브 쪽에서 죽으면 주소만 나온다
ndk-stack에logcat을 물려 주면 함수와 줄로 바꿔 준다-sym은 심볼이 남은obj/local/<abi>를 가리킨다- 그 폴더는 다음 빌드가 덮으므로 배포한 판마다 보관한다
- 자바 호출 뒤에
ExceptionCheck()하고ExceptionClear()로 지운다 - 확인만 하고 안 지우면 다음 호출에서 죽는다
- 값을 넘기기 전에도 검사한다. 둘 다 필요하다
- 오래 들 참조는
NewGlobalRef로 바꾸고DeleteGlobalRef로 놓는다