陳皓
背景
想要使用GDB調試程序,就需要用GNU的編譯器編譯程序。如:用GCC編譯的C/C++的程序,才能用GDB調試。對於Java程序也是一樣的,如果想要用GDB調試,那么就需要用GNU的Java編譯器——GCJ來編譯Java程序。
目前,很多Linux都不會預裝Sun的JVM,取而代之是使用GNU的開源編譯器來編譯和運行Java程序。比如RedHat和Ubuntu,其默認安裝都是使用GNU的Java編譯器(gcj)和解釋器(gij)。當然,它們都被腳本javac和java包裝了起來,你一不小心還以為是使用了Sun的JVM。
為什么GNU要搞出一個Java的編譯和解釋器來呢?其大致有以下幾點:
a) 傳統的JVM太慢了,因為它解釋的是class文件中的bytecode。這種方法實在是太慢了。
b) 為了優化性能,引入了JIT(Just-In-Time),JIT會分析代碼,找出那些被反復調用到一定次數的方法和函數,然后直接把這個方法直接處理成匯編machine code,以后就直接運行機器碼了。
c) 當然,JIT也有問題,一個是startup overhead,就是說啟動的時候有點過分了,表現為時間慢,並且,每次編譯后,都需要JIT重新做來過。另一個問題是JIT比較耗費空間。
d) 傳統的java還有一個比較扯的問題,就是布署起來太麻煩了,需要有N個jar文件,而不是一個可執行文件。並且,Java需要一個很肥大的運行環境。另外,在java和c/c++之間的調用慢得令人受不了。
GNU的Java編譯器GCJ
上述的東西是催生出現gcj的原因,GNU用了Ahead-of-Time Compilation來形容GCJ。GNU對GCJ的出現在理由做了下面的說明:
a) GCC本來可以編譯多種程序語言,所以,把java整進來也是一件make sense(合乎邏輯)的事情。
b) Java的編譯是一件非常簡單的事情,因為沒有C++的模板和預編譯器,而且system type, object model 和 exception handling 也很簡單。所以,這對於擅長編譯技術的GNU來說,從編譯方面優化Java的性能是一些很簡單的事。
c) gcj會對java程序做N多的優化工作,比如:common sub-expression elimination, strength reduction, loop optimization 和 register allocation。在優化方面,是GCJ牛還是JIT牛,存在一些較大的爭論。對於JIT來說,它可以裁剪和做適時優化,因為是在運行時。 Sun的HotSpot技術是其中比較牛的技術,但gcj的技術也不一定就比JIT差。
d) 對於使用gcj的人來說,最大的一個好處就是startup speed和內存空間使用率。啟動JVM或JIT會肖耗很大的內存,例如:NetBean啟動就需要74M的內存(什么事也沒有干), JEmacs使用Swing,一啟動就是26M,而XEmacs只有8M(這些數據是比較老的了,大約在2003年的數據)。
e) 當GCJ剛出道時,有人比較了Kawa Test Suite在GCJ和JDK1.3.1下的運行比較。結果是,GCJ速度比Sun的JIT快兩倍,因為GCJ比Sun的JDK少了一半以上的內存訪問未命中的事情,也就是說少了一半的內存換頁。並且,實際運行過程中,也少了25%的內存使用。
f) 最后,GCJ用的是一個so的庫來做編譯,他可以把.java的程序直接編譯成.o文件和可執行文件。並且用gdb調試。
本文主要講述如果使用GDB調試Java程序。關於GDB的使用,請參看我的另一篇文章《用GDB調試程序》。
用GCJ編譯Java程序
用GCJ編譯Java程序很簡單,關於編譯成.o和執成文件,如下所示:
gcj -c -g -O MyJavaProg.java
gcj -g --main=MyJavaProg -o MyJavaProg MyJavaProg.o
很明顯,基本上就是gcc的語法。當然,你也可以一步編譯出可執行文件:
gcj -g --main=MyJavaProg -o MyJavaProg MyJavaProg.java
其中,使用-g參數表示加入調試信息,這對於調試時相當重要。不然,無法看到實際的源碼和函數。而關於--main參數,意思是指定main函數所在的Java類。
如果你需要使用makefile,想使用類似於CFLAGS這樣的變量,我們可以使用GCJFLAGS這個變量名。
使用GDB調試Java程序
如同我的《用GDB調試程序》一文,我使用如下的Java程序作為演示程序。
1 public class sum{
2 public static long Sum(int n){
3 long result=0, i;
4 for(i=0; i<n; i++){
5 result += i;
6 }
7 return result;
8 }
9
10
11 public static final void main( String argc[] ) {
12 int i;
13 int result=0;
14 for (i=1; i<=100; i++){
15 result += i;
16 }
17 System.out.println("result = "+result);
18 System.out.println("result = "+Sum(1000));
19 }
20 }
下面是程序編譯:(注意-g選項)
hchen@ubuntu:~/java$ gcj --main=sum -g -o sum sum.java
進入GDB環境:
hchen@ubuntu:~/java$ gdb ./sum
GNU gdb 6.6-debian
Copyright (C) 2006 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "i486-linux-gnu"...
Using host libthread_db library "/lib/tls/i686/cmov/libthread_db.so.1".
(gdb)
你可能在直接使用函數名會有以下問題:
(gdb) break sum.main()
Function "sum.main()" not defined.
Make breakpoint pending on future shared library load? (y or [n]) n
目前我不知道是否是GDB的bug,不過Workaround的解決方案如下:
1)先List類的構造函數,這樣可以找到源文件。
2)使用源文件的行號進行break。
(gdb) l sum::sum()
1
2 public class sum{
3 public static long Sum(int n){
4 long result=0, i;
5 for(i=0; i<n; i++){
6 result += i;
7 }
8 return result;
9 }
10
(gdb) l
11
12 public static final void main( String argc[] ) {
13 int i;
14 int result=0;
15 for (i=1; i<=100; i++){
16 result += i;
17 }
18 System.out.println("result = "+result);
19 System.out.println("result = "+Sum(1000));
20 }
(gdb) break 13
Breakpoint 1 at 0x8048d38: file sum.java, line 13.
(gdb) break 16 if i==50
Breakpoint 2 at 0x8048d61: file sum.java, line 16.
運行並調式程序:
對於下面出現在GDB命令我不在作過多解釋,請參看我的《用GDB調試程序》
(gdb) r
Starting program: /home/hchen/java/sum
[Thread debugging using libthread_db enabled]
[New Thread -1243736400 (LWP 18131)]
[New Thread -1245406320 (LWP 18134)]
[Switching to Thread -1243736400 (LWP 18131)]
Breakpoint 1, sum.main(java.lang.String[])void (argc=@2bfa8) at sum.java:14
14 int result=0;
Current language: auto; currently java
(gdb) break sum.Sum <----- 設置函數斷點
Breakpoint 3 at 0x8048b68: file sum.java, line 4.
(gdb) c
Continuing.
Breakpoint 2, sum.main(java.lang.String[])void (argc=@2bfa8) at sum.java:16 <---條件斷點
16 result += i;
(gdb) p result
$2 = 1225
(gdb) n
15 for (i=1; i<=100; i++){
(gdb) c
Continuing.
result = 5050
Breakpoint 3, sum.Sum(int)long (n=1000) at sum.java:4 <----- 函數斷點
4 long result=0, i;
(gdb) bt <----- 打出函數棧
#0 sum.Sum(int)long (n=1000) at sum.java:4
#1 0x08048edf in sum.main(java.lang.String[])void (argc=@2bfa8) at sum.java:19
#2 0xb6b17611 in gnu::java::lang::MainThread::call_main () from /usr/lib/libgcj.so.81
#3 0xb6b86797 in gnu::java::lang::MainThread::run () from /usr/lib/libgcj.so.81
#4 0xb6b29cf3 in _Jv_ThreadRun () from /usr/lib/libgcj.so.81
#5 0xb6ad77dd in _Jv_RunMain () from /usr/lib/libgcj.so.81
#6 0xb6ad7994 in _Jv_RunMain () from /usr/lib/libgcj.so.81
#7 0xb6ad7a1b in JvRunMain () from /usr/lib/libgcj.so.81
#8 0x08048b38 in main (argc=Cannot access memory at address 0x0) at /tmp/ccKMKFB0.i:11
(gdb) n
5 for(i=0; i<n; i++){
(gdb) n
6 result += i;
(gdb) n
5 for(i=0; i<n; i++){
(gdb) finish <----- 退出函數
Run till exit from #0 sum.Sum(int)long (n=1000) at sum.java:5
0x08048edf in sum.main(java.lang.String[])void (argc=@2bfa8) at sum.java:19
19 System.out.println("result = "+Sum(1000));
Value returned is $1 = 499500
(gdb) n
result = 499500
0xb6b17611 in gnu::java::lang::MainThread::call_main () from /usr/lib/libgcj.so.81
(gdb) info thread <----- 查看線程
2 Thread -1245553776 (LWP 18143) 0xffffe410 in __kernel_vsyscall ()
* 1 Thread -1243883856 (LWP 18142) 0xb6b17611 in gnu::java::lang::MainThread::call_main () from /usr/lib/libgcj.so.81
(gdb)
其它注意事項
當你使用GDB調試被GCJ編譯的程序時,你需要讓GDB忽略SIGPWR和SIGCPU這兩個信號。這兩個信號被垃圾回收器使用,為了讓調試工作進行的更順利,我們需要使用GDB的命令來忽略這兩個信號:
(gdb) handle SIGPWR nostop noprint
Signal Stop Print Pass to program Description
SIGPWR No No Yes Power fail/restart
(gdb) handle SIGXCPU nostop noprint
Signal Stop Print Pass to program Description
SIGXCPU No No Yes CPU time limit exceeded
當然,你並不用每次都需要設置這兩個命令,你可以設置$HOME目錄下的.gdbinit文件來把這兩個命令作為GDB的初始化選項。
參考文章
(轉載時請注明作者和出處。未經許可,請勿用於商業用途)
更多文章請訪問我的Blog: http://blog.csdn.net/haoel