2011년 11월 12일 토요일

jdb로 android 디버깅하기

Android model을 개발하다 보면 가끔씩 Java code를 line debugging이 필요할 때가 있다. line debugging 뿐만 아니라 폰에서 Applicatoin lockup과 같은 현상이 발생할 때 debugger에 attach를 해서 debugging을 해야할 상황도 종종 발생한다.

Android의 application들은 많은 부분을 Java로 구현되어 있으며 이들을 debugging하기 위한 많은 Tool들이 있다. 대표적인 것이 eclipse이며, 그 외로 intellJ나 jswat(java debugger front-end)같은 것들이 있다. 하지만, eclipse와 같은 IDE(Integrated Development Environment)는 개발과 debugging을 편리하게 해 주지만, 실행 및 동작에 느린감이 조금 있으며, project를 구성해야하는 불편함도 있다.

Application을 개발하는 과정이라면 IDE Tool을 선택해서 사용하는 것이 좋은 선택일 것이다. 그러나, 갑자기 debugging이 필요한 경우가 발생할 때가 있고, 이 때 IDE Tool을 설치해서 debugging하기란 쉽지 않은 상황이 생긴다.

Android Phone을 개발하는 과정에서 Hopper Test나 Automatic Test를 진행하다 보면 이런 경우를 종종 만난다. 입력기(Keypad)가 올라와서 내려가지 않는 경우, Application이 특정 상황에서 계속 죽는 경우등이 발생할 경우 로그로만으로 분석이 불가능한 상황이 많다. 이럴 때, 간단하게 debugging을 할 수 있으면 문제를 잡는데 큰 도움이 될 수 있다.

jdb는 가장 기본적인 Java Debugger로써 command console을 통한 debugging을 지원한다(gdb와 유사하다). project를 만들 필요도 없고, 실행속도도 빠르며, 특정 위치에서 break, object 및 변수값 확인이 쉽고 간결하다. 물론 jdb로 debugging시에도 debugging하려는 class의 Source code가 반드시 있어야 한다. (실행되고 있는 class의 source code와 동일해야 한다.) jdb는 JAVA SDK를 설치하면 같이 설치된다. (JAVA SDK는 설치를 했겠지... ... Android Phone을 개발하는데 이것도 설치하지 않았다는건...)

이제 jdb를 이용한 debugging 방법을 간략하게 이야기한다.

 

1. Android SDK를 설치하고, Android phone과 PC를 USB cable로 연결한다.

- Window OS라면 Android phone의 USB Device drvier를 설치해야 한다.

- Linux는 Android SDK만 설치하면 된다.

- Phone에서 설정->Application->USB 디버깅을 선택해야 한다. (이런것까지 이야기할 필요는 없겠지만... ...)

 

2. DDMS를 실행시킨다.

- DDMS로 debugging 할 Applicatoin의 port를 확인한다.

DDMS

- 위 그림에서 process name과 pid, debug port를 확인할 수 있다.

- system_server의 경우 8600번 port가 할당되어 있는 것을 알 수 있다.

- adb로 port number를 알아올 방법을 모르고 있다. 누가 알고 있으면 좀 알려주세요!!!!

 

아래 내용 추가 ->

ddms가 실행되고 있지 않을 경우에는 tcp port가 target이랑 연결되지 않는다.

ddms를 종료할 경우에는 아래와 같이 수행한다.

"adb forward tcp:8600 tcp:8600"을 command 창에 입력하여 target과 연결 시킨다.

- 8600번 port는 system server에 할당된 것으로 debugging하려고 하는 process의 port number를 사용한다.


3. port를 확인하고 command console에서 jdb를 실행시킨다.

- jdb의 실행은 connect option과 sourcepath option을 주고 실행시킨다.

jdb -connect com.sun.jdi.SocketAttach:hostname=localhost,port=8600 -sourcepath ./frameworks/base/services/java

- port number를 debugging하려는 process의 port number를 할당한다. 위의 command는 system_server를 debugging하기 위해서 8600번 port number를 사용했다.

- sourcepath는 debugging하려는 class의 source의 위치이다. (위에서 언급했듯이 debugging할 class와 실행되고 있는 process의 class의 source는 일치해야 한다.)

- sourcepath는 전체경로나 상대경로 모두 가능하다. 상대경로인 경우에는 jdb가 실행된 위치가 기준 path가 된다. 위 예제는 상대경로이다.

- sourecpath option은 약간의 주의를 기울여야 한다. sourcepath를 실제 source가 있는 directory를 설정하는 것이 아니고 package와 동일하게 맞춰야 한다.

* 예를 들어, system_server에서 실행되고 있는 watchdog thread를 debugging하려고 한다. Watchdog class의 run method가 thread function이기 때문에 run 함수를 debugging해야 한다. Watchdog class는 Android framework soruce에서 ./android/frameworks/base/services/java/com/android/server/Watchdog.java에 구현되어 있다.

* 하지만, Watchdog class를 debugging하기 위해서 ./frameworks/base/services/java/com/android/server 를 설정해서는 안된다. java는 package라는 개념이 존재하고 package를 절대 간과해서는 안된다. 결국, sourcepath는 package name과 일치하는 path의 바로 위 path가 된다.(무슨 말인지... ㅠㅠ)

* Watchdog class의 package name은 com.android.server.Watchdog 이다. Watchdog class를 debugging하기 위해서는 ./android/frameworks/base/services/java/ 로 sourcepath를 설정해야 한다. pacakge name을 보면 "com.android.server"은 ./android/frameworks/base/services/java/ 의 하위 path라는 것을 알 수 있다.

* sourcepath option은 ":"로 여러개를 추가할 수 있고, jdb 실행중에도 sourcepath command로 추가 및 변경이 가능한다.

 

4. jdb를 command console에서 여러가지 명령어로 debugging을 수행한다.

- jdb가 정상적으로 수행되고 연결이되며 위와 같은 화면이 나온다.

- jdb가 연결된 상태에서 아직까지는 모든 thread가 running 상태이다.

- command console에서 threads라는 명령어를 입력해 보자. 실행되고 있는 thread들의 정보를 볼 수 있다. 처음 열에 나오는 숫자가 thread id이며, 두번째 열에는 thread name, 마지막 3번째 열에는 thread의 상태가 나온다.(thread id는 중요하면 반드시 알아두자!)

- watchdog thread의 id는 0xc140588a8이다. 위 그림에서 밑에서 2번째 line에 보인다.

- 이제 모든 thread들을 suspend 시키고 watchdog thread를 debugging 해 보자.

- suspend 명령어를 입력하면 모든 thread들이 suspend 된다. (특정 thread만 멈출려면 susepnd <thread id>를 하면 된다.)

- suspend가 되면 "All threads suspended."가 출력된다.

- debugging하려는 thread를 선택해야 하기 때문에 thread 명령어를 사용한다. "thread <thread id>"를 입력하면 thread를 선택할 수 있다.

- watchdog thread를 선택하기 위해서 "thread 0xc140588a8"을 입력한다. 

- thread를 선택한 후에 where 명령어를 입력하면 현재 stack을 보여준다. ("where all"을 입력하면 모든 thread의 stack을 보여준다.)

- list 명령어를 입력하여 source code를 확인할 수 있다. 위 그림에서는 watchdog thread가 native method에 위치해 있기 때문에 list 명령어를 입력해도 source code가 보이지 않는다.

- stack을 위로 이동시켜보자. stack은 up 또는 down 명령어로 이동할 수 있다. 

- sourcepath에 source가 없을 경우에는 "Source file not found"가 출력된다.

- up 명령어를 입력하면 stack이 위로 이동하고 where 명령어로 위치를 확인할 수 있다.

- watchdog thread의 stack을 Watchdog.java 파일의 Watchdog class의 run함수까지 이동시킨다. (up을 2번 실행)

- stack을 이동시킨 후 list 명령어를 입력하여 현재 위치를 source code에서 확인할 수 있다. (sourcepath에 source가 있는 경우)

- "list <line number>" 명령어로 line을 이동하면서 source code를 볼 수 있다.

- locals 명령어로 현재 stack상의 local variable의 값을 볼 수 있다.

- 특정 변수의 값을 보려면 print 명령어를 사용한다. "print <변수명>"을 입력하면 특정 변수의 값을 볼 수 있다.

- 현재 class(= this)의 멤버변수의 값을 보려면 print 명령어나 dump 명령어를 사용한다.

- "print mForceKillSystem"을 입력하면 mForceKillSystem의 값을 확인할 수 있다.

- dump 명령어는 object의 모든 member를 보여준다. object가 primitive type일 경우에는 값만 보여준다.

- break point를 설정해 보자.

- break point는 stop 명령어로 설정한다. stop 명령어는 "stop at" 과 "stop in" 2가지가 있다. "stop at"은 line에 break point를 설정하는 것이고, "stop in"은 method의 진입에 break point를 설정하는 것이다.

- Watchdog.java의 404 line에 break point를 설정한다.

- break point 설정에 주의해야할 사항이 있다. 역시나 package이다!!!!!

- break point를 설정할 때도 package name을 사용해야 한다.

- "stop at com.android.server.Watchdog:404" 명령어로 Watchdog.java의 404 line에 break point를 설정한다. ("stop at <class id>:<line>"으로 설정한다. <class id>는 "a full class name with package qualifiers"이다.)

- break point를 설정하고 resume 명령어를 입력하여 thread들을 재실행시킨다.

- break point에서 break가 되면 "Breakpoint hit"가 출력되고 break가 걸린 위치를 보여준다.

- break가 된 상태에서 원하는 정보를 print나 dump, where등으로 확인해보자!!! 

- help 명령어를 입력하면 jdb에서 사용할 수 있는 명령어들이 나온다. 적절히 참고하여 debugging하자!!!!

 

jdb의 여러 명령어들을 이용하여 간단한 java debugging을 수행할 수 있다. 이것은 간단하지만, 강력하다!

jdb는 Application 개발자뿐만 아니라 Android Framworks 개발자들도 debugging시에 강력한 도구로써 사용될 수 있다.

꼭 기억해두자!!

댓글 없음:

댓글 쓰기