ㅈㅅㄹ

순수 자바 어플리케이션이라면 디버그가 꽤나 쉽지만 JNI를 쓴 앱에서 native쪽에서 죽는 문제를 디버그하려면 일단 눈물 부터 닦고 시작해야 하는 게 안드로이드다. 물론 이전에 쓴 안드로이드 Native단에서 GDB로 디버그하기의 방법으로 gdb로 편하게 디버그 할 수도 있지만, 세상일이 언제나 호락호락하게 이쪽 형편에 맞춰 주는 게 아니라서, 가령 특정 단말에서만 Native에서 죽는데 단말 바이너리가 개발용이 아니라서 gdbserver가 탑재 되어 있지도 않고 탑재 시킬수도 없는 상황이라든가 디버그하다가 오히려 gdbserver가 미쳐서 세그멘테이션 폴트로 계속 죽는다든가 뭐 그런 상황이 언제든지 찾아 올 수 있다.

안드로이드 앱이 native 레이어에서 죽으면 보통 다음과 같은 로그를 뿌리게 된다. 

D/dalvikvm( 3130): GC_CONCURRENT freed 461K, 53% free 2874K/6087K, external 452K/1028K, paused 2ms+1ms
I/DEBUG   ( 2573): *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
I/DEBUG   ( 2573): Build fingerprint: 'samsung/SHW-M250K/SHW-M250K:2.3.3/GINGERBREAD/ED12:eng/test-keys'
I/DEBUG   ( 2573): pid: 16568, tid: 16577  >>> com.kwac.widgetmanager <<<
I/DEBUG   ( 2573): signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 00000000
I/DEBUG   ( 2573):  r0 00000000  r1 00000001  r2 002191b8  r3 00000000
I/DEBUG   ( 2573):  r4 81da96bc  r5 81da0ae4  r6 00000000  r7 00000000
I/DEBUG   ( 2573):  r8 00000001  r9 81da0ae4  10 46927e54  fp 000fc788
I/DEBUG   ( 2573):  ip 81da0b34  sp 46b3a7b0  lr fffff5a4  pc 81bbbfa0  cpsr 00000030
I/DEBUG   ( 2573):  d0  3f80000041800000  d1  0000000041800000
I/DEBUG   ( 2573):  d2  0000000000000000  d3  0000000000000000
I/DEBUG   ( 2573):  d4  0000000500000000  d5  41d397c7f2000000
I/DEBUG   ( 2573):  d6  408f400000000000  d7  42732239425fd0e1
I/DEBUG   ( 2573):  d8  0000000000000000  d9  0000000000000000
I/DEBUG   ( 2573):  d10 0000000000000000  d11 0000000000000000
I/DEBUG   ( 2573):  d12 0000000000000000  d13 0000000000000000
I/DEBUG   ( 2573):  d14 0000000000000000  d15 0000000000000000
I/DEBUG   ( 2573):  d16 0000000000000001  d17 3ff0000000000000
I/DEBUG   ( 2573):  d18 3ff0000000000000  d19 0000000000000000
I/DEBUG   ( 2573):  d20 0000000000000000  d21 0000000000000000
I/DEBUG   ( 2573):  d22 3ff0000000000000  d23 0000000000000000
I/DEBUG   ( 2573):  d24 3ff0000000000000  d25 0000000000000000
I/DEBUG   ( 2573):  d26 0000000000000000  d27 0000000000000000
I/DEBUG   ( 2573):  d28 0002aaa80002aaa8  d29 0002aaa80002aaa8
I/DEBUG   ( 2573):  d30 0001000000010000  d31 0001000000010000
I/DEBUG   ( 2573):  scr 60000010
I/DEBUG   ( 2573): 
I/DEBUG   ( 2573):          #00  pc 003bbfa0  /data/data/com.kwac.widgetmanager/lib/libwacrt.so
I/DEBUG   ( 2573):          #01  pc 002358f2  /data/data/com.kwac.widgetmanager/lib/libwacrt.so
I/DEBUG   ( 2573):          #02  pc 0023596a  /data/data/com.kwac.widgetmanager/lib/libwacrt.so
I/DEBUG   ( 2573):          #03  pc 0029ae72  /data/data/com.kwac.widgetmanager/lib/libwacrt.so
I/DEBUG   ( 2573):          #04  pc 002ac342  /data/data/com.kwac.widgetmanager/lib/libwacrt.so
I/DEBUG   ( 2573):          #05  pc 0016a0c4  /data/data/com.kwac.widgetmanager/lib/libwacrt.so
I/DEBUG   ( 2573):          #06  pc 0016db78  /data/data/com.kwac.widgetmanager/lib/libwacrt.so
I/DEBUG   ( 2573):          #07  pc 00250044  /data/data/com.kwac.widgetmanager/lib/libwacrt.so
I/DEBUG   ( 2573):          #08  pc 0025045a  /data/data/com.kwac.widgetmanager/lib/libwacrt.so
I/DEBUG   ( 2573):          #09  pc 002508fc  /data/data/com.kwac.widgetmanager/lib/libwacrt.so
I/DEBUG   ( 2573):          #10  pc 00250bfe  /data/data/com.kwac.widgetmanager/lib/libwacrt.so
I/DEBUG   ( 2573):          #11  pc 00242808  /data/data/com.kwac.widgetmanager/lib/libwacrt.so
I/DEBUG   ( 2573):          #12  pc 00157b8a  /data/data/com.kwac.widgetmanager/lib/libwacrt.so
I/DEBUG   ( 2573):          #13  pc 00157bc4  /data/data/com.kwac.widgetmanager/lib/libwacrt.so
I/DEBUG   ( 2573):          #14  pc 0014fada  /data/data/com.kwac.widgetmanager/lib/libwacrt.so
I/DEBUG   ( 2573):          #15  pc 00151a48  /data/data/com.kwac.widgetmanager/lib/libwacrt.so
I/DEBUG   ( 2573):          #16  pc 00151c32  /data/data/com.kwac.widgetmanager/lib/libwacrt.so
I/DEBUG   ( 2573):          #17  pc 00151c56  /data/data/com.kwac.widgetmanager/lib/libwacrt.so
I/DEBUG   ( 2573):          #18  pc 00151fde  /data/data/com.kwac.widgetmanager/lib/libwacrt.so
I/DEBUG   ( 2573):          #19  pc 002edda8  /data/data/com.kwac.widgetmanager/lib/libwacrt.so
I/DEBUG   ( 2573):          #20  pc 0015159e  /data/data/com.kwac.widgetmanager/lib/libwacrt.so
I/DEBUG   ( 2573):          #21  pc 002ef050  /data/data/com.kwac.widgetmanager/lib/libwacrt.so
I/DEBUG   ( 2573):          #22  pc 00158c54  /data/data/com.kwac.widgetmanager/lib/libwacrt.so
I/DEBUG   ( 2573):          #23  pc 001589ac  /data/data/com.kwac.widgetmanager/lib/libwacrt.so
I/DEBUG   ( 2573):          #24  pc 002ede0c  /data/data/com.kwac.widgetmanager/lib/libwacrt.so
I/DEBUG   ( 2573):          #25  pc 0014fb4a  /data/data/com.kwac.widgetmanager/lib/libwacrt.so
I/DEBUG   ( 2573):          #26  pc 00151864  /data/data/com.kwac.widgetmanager/lib/libwacrt.so
I/DEBUG   ( 2573):          #27  pc 00249380  /data/data/com.kwac.widgetmanager/lib/libwacrt.so
I/DEBUG   ( 2573):          #28  pc 00017eb4  /system/lib/libdvm.so
I/DEBUG   ( 2573):          #29  pc 00049ae4  /system/lib/libdvm.so
I/DEBUG   ( 2573):          #30  pc 0001d0c8  /system/lib/libdvm.so
I/DEBUG   ( 2573):          #31  pc 00022398  /system/lib/libdvm.so
I/DEBUG   ( 2573): 
I/DEBUG   ( 2573): code around pc:
I/DEBUG   ( 2573): 81bbbf80 62637022 1c58e00a 0063eb00 ff9cf7ff 
I/DEBUG   ( 2573): 81bbbf90 69e26a63 f8421c59 62617023 e018f8df 
I/DEBUG   ( 2573): 81bbbfa0 f8556831 6019300e 81f0e8bd 0014a6a7 
I/DEBUG   ( 2573): 81bbbfb0 001e4bb4 001ed780 fffff5a4 001ed748 
I/DEBUG   ( 2573): 81bbbfc0 bf004770 4b0bb510 46047a02 b10a447b 
I/DEBUG   ( 2573): 
I/DEBUG   ( 2573): code around lr:
I/DEBUG   ( 2573): fffff584 ffffffff ffffffff ffffffff ffffffff 
I/DEBUG   ( 2573): fffff594 ffffffff ffffffff ffffffff ffffffff 
I/DEBUG   ( 2573): fffff5a4 ffffffff ffffffff ffffffff ffffffff 
I/DEBUG   ( 2573): fffff5b4 ffffffff ffffffff ffffffff ffffffff 
I/DEBUG   ( 2573): fffff5c4 ffffffff ffffffff ffffffff ffffffff 
I/DEBUG   ( 2573): 
I/DEBUG   ( 2573): stack:
I/DEBUG   ( 2573):     46b3a770  46927e54  
I/DEBUG   ( 2573):     46b3a774  afd14173  /system/lib/libc.so
I/DEBUG   ( 2573):     46b3a778  81da96bc  
I/DEBUG   ( 2573):     46b3a77c  00000001  
I/DEBUG   ( 2573):     46b3a780  002191b8  
I/DEBUG   ( 2573):     46b3a784  00000000  
I/DEBUG   ( 2573):     46b3a788  00000001  
I/DEBUG   ( 2573):     46b3a78c  afd14789  /system/lib/libc.so
I/DEBUG   ( 2573):     46b3a790  81da96bc  
I/DEBUG   ( 2573):     46b3a794  81c3f393  /data/data/com.kwac.widgetmanager/lib/libwacrt.so
I/DEBUG   ( 2573):     46b3a798  81da96bc  
I/DEBUG   ( 2573):     46b3a79c  81bbbee9  /data/data/com.kwac.widgetmanager/lib/libwacrt.so
I/DEBUG   ( 2573):     46b3a7a0  81da96bc  
I/DEBUG   ( 2573):     46b3a7a4  81da0ae4  
I/DEBUG   ( 2573):     46b3a7a8  df002777  
I/DEBUG   ( 2573):     46b3a7ac  e3a070ad  
I/DEBUG   ( 2573): #00 46b3a7b0  46b3a7e0  
I/DEBUG   ( 2573):     46b3a7b4  00000000  
I/DEBUG   ( 2573):     46b3a7b8  001b07f8  
I/DEBUG   ( 2573):     46b3a7bc  00000000  
I/DEBUG   ( 2573):     46b3a7c0  00000001  
I/DEBUG   ( 2573):     46b3a7c4  81a358f7  /data/data/com.kwac.widgetmanager/lib/libwacrt.so
I/DEBUG   ( 2573): #01 46b3a7c8  46b3a7e0  
I/DEBUG   ( 2573):     46b3a7cc  0019ff68  
I/DEBUG   ( 2573):     46b3a7d0  001b07f8  
I/DEBUG   ( 2573):     46b3a7d4  00000000  
I/DEBUG   ( 2573):     46b3a7d8  00000001  
I/DEBUG   ( 2573):     46b3a7dc  81a3596f  /data/data/com.kwac.widgetmanager/lib/libwacrt.so

위의 콜 스택을 보면 JNI로 호출 된 libwacrt 라이브러리 내에서 뭔가 열심히 일하다 죽었다는 것을 알 수 있지만 심볼 테이블이 나오지 않기 때문에 실제 어떤 함수를 호출하다가 죽었는지는 표시되지 않는다. 그러나 strip 되지 않은 라이브러리를 objdump 시킨 후 스택 트레이스에 찍힌 PC 값을 기반으로 뭘 하다 죽었는지를 알아 낼 수 있다는 게 이 글의 핵심이다.

방법은 간단하다. 안드로이드 소스나 NDK에 포함된 objdump를 사용하는 것으로,  objdump 파일은 (리눅스의 경우) prebuilt/linux-x86/arm-eabi-[버전]/bin/ 내에 있다.

$ arm-eabi-objdump -dR libwacrt.so > libwacrt.asm

위와 같이 실행하면 libwacrt.asm 파일이 만들어지게 되며 asm 파일을 열어서 스택 트레이스에 찍힌 주소를 검색해 보면 손쉽게 어디서 죽었는지 확인할 수 있다.

00249348 <_ZN7androidL12DestroyFrameEP7_JNIEnvP8_jobject>:
  249348: e92d 41f0  stmdb sp!, {r4, r5, r6, r7, r8, lr}
  24934c: 4c19       ldr r4, [pc, #100] (2493b4 <_ZN7androidL12DestroyFrameEP7_JNIEnvP8_jobject+0x6c>)
  24934e: 460e       mov r6, r1
  249350: 4605       mov r5, r0
  249352: 447c       add r4, pc
  249354: 68e2       ldr r2, [r4, #12]
  249356: f7ff f895  bl 248484 <_ZN7_JNIEnv11GetIntFieldEP8_jobjectP9_jfieldID>
  24935a: 4607       mov r7, r0
  24935c: f71f fb34  bl 1689c8 <_ZNK7WebCore5Frame4viewEv>
  249360: 4604       mov r4, r0
  249362: 6840       ldr r0, [r0, #4]
  249364: 1c43       adds r3, r0, #1
  249366: 4638       mov r0, r7
  249368: 6063       str r3, [r4, #4]
  24936a: f71f fb27  bl 1689bc <_ZNK7WebCore5Frame6loaderEv>
  24936e: 4680       mov r8, r0
  249370: 4638       mov r0, r7
  249372: f71f fbe3  bl 168b3c <_ZNK7WebCore5Frame4pageEv>
  249376: 4607       mov r7, r0
  249378: f1b8 0f00  cmp.w r8, #0 ; 0x0
  24937c: d002       beq.n 249384 <_ZN7androidL12DestroyFrameEP7_JNIEnvP8_jobject+0x3c>
  24937e: 4640       mov r0, r8
  249380: f708 fa64  bl 15184c <_ZN7WebCore11FrameLoader16detachFromParentEv>
  249384: b12f       cbz r7, 249392 <_ZN7androidL12DestroyFrameEP7_JNIEnvP8_jobject+0x4a>
  249386: 4638       mov r0, r7
  249388: f727 f80c  bl 1703a4 <_ZN7WebCore4PageD1Ev>
  24938c: 4638       mov r0, r7
  24938e: f6c5 ffcf  bl 10f330 <_ZN3WTF8fastFreeEPv>
  249392: 1d20       adds r0, r4, #4
  249394: f705 f876  bl 14e484 <_ZN3WTF10RefCountedIN7WebCore6WidgetEE5derefEv>
  249398: f8df c01c  ldr.w ip, [pc, #28] ; 2493b8 <_ZN7androidL12DestroyFrameEP7_JNIEnvP8_jobject+0x70>
  24939c: 682a       ldr r2, [r5, #0]
  24939e: 4628       mov r0, r5
  2493a0: 44fc       add ip, pc
  2493a2: 4631       mov r1, r6
  2493a4: 2300       movs r3, #0
  2493a6: f8d2 41b4  ldr.w r4, [r2, #436]
  2493aa: f8dc 200c  ldr.w r2, [ip, #12]
  2493ae: 47a0       blx r4
  2493b0: e8bd 81f0  ldmia.w sp!, {r4, r5, r6, r7, r8, pc}
  2493b4: 0035d40e  .word 0x0035d40e
  2493b8: 0035d3c0  .word 0x0035d3c0

이와 같은 방법으로 유추한 콜 스택은:

....귀찮다 위쪽은 생략...
WebCore::ResourceLoader::cancel()
WebCore::DocumentLoader::stopLoading(DatabasePolicy)
WebCore::FrameLoader::stopAllLoaders(DatabasePolicy)
WebCore::FrameLoader::detachFromParent()
android::DestroyFrame(JNIEnv*)

이 되겠다.

%%P.S. 페북에서 누가 댓글 달아서 알게 됐는데 objdump 대신 addr2line을 써도 된다. 오히려 더 편한가??
$ arm-eabi-addr2line -e libwacrt.so 00249380
[소스 위치 어딘가...]/WebCoreFrameBridge.cpp:1044