Sep 19 2008

JNI 를 통해 사용하는 VC++ 라이브러리를 디버깅하기

분류: Lang.Java 태그: ,, , , , , Heart @ 4:21 오후

Trackback : http://dev.heartsavior.net/archives/205/trackback/

JNI를 사용하는 프로젝트 수행 중에 JNI 단독으로 테스트할 때는 일어나지 않는 오류가 계속 일어나길래, JNI 가 사용하는 VC++ 라이브러리 내부를 디버깅하는 방법을 찾아 보았다.

@ Java Native Interface (JNI) - Debugging C code in a dll

설명이 잘 되어있긴 한데, 정리는 좀 덜 되어 있어서 실제로 디버깅 테스트를 해 보고 절차를 정리해 보았다.

IDE는 Visual C++ 6 를 기준으로 설명한다. 다만, 설정해야 할 부분이 별로 없으므로 아래의 설정은 타 IDE에도 적용이 가능할 것 같다.

1. Project Settings 에서 JNI 구현이 되어 있는 프로젝트를 선택함
2. Settings For: 를 Debug로 맞춘 다음(혹은 Debug 옵션들이 선택된 Custom Configuration도 상관없음) Debug 탭 선택
3. Executable for debug session: JRE 의 java.exe 나 javaw.exe 를 선택
4. Working directory 에 JNI를 사용하는 프로젝트의 root 디렉토리를 전체 경로로 기록
(Eclipse 의 경우 debug / run 시에 해당 디렉토리를 Working directory 로 사용하므로 그에 맞춰주는 것임)
5. Program arguments 에
-Xrunjdwp:transport=dt_socket,server=y,address=<Eclipse 에서 원격 디버깅을 붙을 port>,suspend=n -classpath <jar파일들;class 디렉토리들(Eclipse라면 bin)> <Main 클래스 경로(패키지.클래스명)> <main() 의 arguments> 기록
(classpath 를 적을 때 Working directory 의 상대 경로로 적어도 된다.)

* 중요 포인트 : JNI 라이브러리를 로드하는 부분( System.loadLibrary() 나 System.load() ) 에서 IDE에서 빌드하는 결과 파일을 로드하도록 해 주어야 한다.
참고로, 라이브러리 경로를 절대 경로로 표현하려면 System.loadLibrary() 대신 System.load() 를 사용하여야 하고, 이 때는 경로에 확장자도 붙여 준다.

디버그할 때는 VC++ 에서 Configuration 을 위에서 설정한 대상(Debug) 으로 맞추고 F5를 눌러 실행하면 JVM이 실행된 Console 이 뜬다.  VC++ 에서 break 걸리면 VC++ 디버거 사용 방법대로 디버깅 하면 된다.

VC++ 6 에서 실행할 때 native method 를 호출하는 위치까지는 진행되어야 breakpoint라도 걸린다. (F11로 실행해 봐야 JVM Disassembly 가 뜨기 때문에 별 도움이 안됨.)

위와 같은 방법으로 JNI 를 통해 호출되는 라이브러리를 디버깅할 수 있다.

—-

다음으로, 양 쪽으로 모두 디버그 하는 방법을 알아보자. (Eclipse 3.3 기준)

VC++ 6 은 위와 설정이 같다.

Eclipse 에서는 아래와 같이 설정한다. (다른 IDE를 쓴다면 해당 IDE 가 원격 디버깅하는 방법대로 하면 됨)

1. Open Debug Dialog 를 실행한다.
2. Remote Java Application 을 선택하고 New 를 선택한다.
3. Main 탭에서 Project 를 선택하고, Connection Properties 에서 Port 를 VC++ 에서 설정한 port 를 적는다.

해당 Debug Configuration 을 실행하면 VC++ 가 실행한 JVM 에 원격 디버거로 붙게 된다.

주의해야 할 점은, 프로그램 로직이 짧으면 Eclipse 가 연결하려는 시도 중에 프로그램이 끝나버린다.
또한, VC++ 에서 break 가 걸린 상태에 Eclipse에서 연결을 시도하면, VC++ 에서 break 풀릴 때까지 Eclipse 는 연결이 안되는 것 같다. 즉, 연결에 대한 처리가 delay 되는 것 같다. (확실하지는 않음. 혹시 아시는 분은 리플로 알려주시면 감사하겠습니다.)

양 쪽으로 디버깅할 때에는, Java 프로그램이 시작할 때 native 의 초기 진입점(native method) 을 바로 실행하도록 하면 양쪽으로 디버그를 걸기가 좀 편하다.
초기 진입점에 breakpoint 걸고, VC++에서 멈춰져 있는 상태가 되면 Eclipse 에서 원격 디버거로 연결하고, VC++ 에서 바로 F5를 눌러 초기 진입점을 빠져나가면 (뭐 이리 복잡하냐 -_-) 이후로는 양쪽으로 breakpoint 에서 멈추게 된다.

어쨌든 절차도 복잡하고… 가장 좋은 방법은 JNI 로 사용할 라이브러리를 ‘디버깅할 일이 없도록’ 하는 것이겠다.

ps. 특정 IDE 에서 컴파일해야 하는 게 아니라면 Eclipse 의 CDE를 통해 Eclipse 내부에서 북치고 장구치는 것도 가능하다.

@ Integrated Debugger for Java*/JNI Environments


Aug 26 2008

순수 자바를 이용하여 JVM을 죽게 하는 프로그램을 어떻게 짤까?

분류: Lang.Java 태그: ,, , , Heart @ 1:36 오전

Trackback : http://dev.heartsavior.net/archives/182/trackback/

순수 자바를 이용하여 JVM을 죽게 하는 프로그램을 어떻게 짜죠?

‘사랑하지 않으면 떠나라’ 의 한 챕터에서 제시하는, 자바 고급 프로그래머에 대한 면접 내용이다.

처음에 이 문제를 접했을 때 잠깐 착각을 해서 ‘순수 자바를 이용하여’ 라는 부분을 염두에 두지 못했다.

이 전제가 없으면 가장 편하게 생각할 수 있는 방법은 JNI 이다. JNI에서 C/C++ 라이브러리를 호출하고, 라이브러리 내부에서 런타임 에러로 crash 상태를 만들면(잘못된 메모리 참조 같은 쉬운 방법을 이용하자) JVM 도 crash 가 발생하게 된다. 견고한 JVM 을 피해서 돌아가는 답안이라고 할 수 있다. 그만큼 JNI 라이브러리를 작성할 때는 심혈을 기울여야 한다는 뜻이기도 하다.

하지만, ‘순수 자바를 이용하여’ 라는 전제가 붙는 것을 확인하고는 답을 낼 수 없었다. 자바 코드 내에서 발생하는 오류는 모두 Exception 이나 Error 로 잡힌다고 생각했던 것도 있고, JVM 내부는 공부해 본 적이 없기 때문이기도 했다.

그러다 오늘, 프로그래밍 갤러리에서 DriverManager Vulnerability 라는 글이 올라왔고, 약간의 논쟁 후에 immutable 속성인 String 클래스의 private field 를 수정하는 아이디어가 나타났다.
reflection을 몰라서 가능할 것이라고는 전혀 생각하지 못했다.
근데… reflection을 통해서 String 클래스의 private field 를 변경하는 글이 올라왔다. (첫번째 글 / 두번째 글)
이것 자체로도 나에게는 상당히 신기한 일이었다.
C/C++ 언어를 주로 다뤘던지라, 캡슐화는 견고함을 보장한다라고 생각을 하고 있었는데, reflection 으로 private field의 access 권한을 획득해버렸다.

근데, 첫번째 글의 결과를 보니… JVM이 죽었다!?

글에 적힌 crash 로그를 살펴보니 jre 1.5 버전이다.
같은 코드를 eclipse 에서 구현하여 해당 프로젝트의 JDK와 JRE를 1.5로 맞추었다.

String abc = "abc";
String def = "abc";
Field declaredField = String.class.getDeclaredField("value");
declaredField.setAccessible(true);
char[] abcArray = (char[]) declaredField.get(abc);
 
abcArray[0] = 'd';
 
System.out.println(def);

해당 코드를 호출했다. 역시, JVM이 crash되었다.

#
# An unexpected error has been detected by HotSpot Virtual Machine:
#
#  EXCEPTION_ACCESS_VIOLATION (0xc0000005) at pc=0×00aea4fc, pid=1764, tid=1672
#
# Java VM: Java HotSpot(TM) Client VM (1.5.0_16-b02 mixed mode, sharing)
# Problematic frame:
# j  net.heartsavior.dev.JVMDeath.crashJVMUsingStringObject()V+24
#
# An error report file with more information is saved as hs_err_pid1764.log
#
# If you would like to submit a bug report, please visit:
#   http://java.sun.com/webapps/bugreport/crash.jsp
#

뭐… 그래서 결론은 JVM 이 죽는 코드를 ‘구현했다’ 가 아닌 ‘발견했다’ 이고, 그나마 위의 코드는 1.6에서는 통하지 않는다…;;
위의 코드를 1.6에서 돌리면 정상적으로(라고 해야될라나) ‘def’ 가 출력된다.
( 이게 1.5만의 버그인지, 1.5 이하 공통 버그였다가 1.6에서 수정된 것인지는 잘 모르겠다. 귀찮다;; )

ps. 잘 생각해 보면 1.6도 JVM 이 죽지 않는다 뿐이지, String 클래스가 immutable 에서 해방될 수 있다…
즉, ” ‘뻘짓을 하지 않고 정식 루트로 접근할 경우’ String 클래스는 immutable 이다 ” 라고 할 수 있겠다. -_-;;


Aug 11 2008

Thread Pool 을 별다른 설정 없이 간단하게 사용하는 방법

분류: Lang.Java 태그: ,, Heart @ 6:14 오후

Trackback : http://dev.heartsavior.net/archives/177/trackback/

* Thread Pool 을 별다른 설정 없이 간단하게 사용하는 방법

ExecutorService service = Executors.newFixedThreadPool(CONCURRENT_THREAD_COUNT);

service.execute(new Runnable() { public void run() { … } });

service.shutdown();

(짧기 때문에 code-highlight 를 적용하지 않았음)

java.util.concurrent.Executor와 그의 친구들을 사용하면 두세 줄로 해결

이런 유용한 메소드를 지원해 준다는 건 상당히 마음에 든다. J2SE 만세~!!

ps. 좀 더 복잡하게 사용하는 방법은 아래 링크 참조(매번 이런 식이지…)

@ ThreadpoolExecutor


뒷 쪽 »