天天看點

用GDB 調試Java程式背景GNU的Java編譯器GCJ用GCJ編譯Java程式使用GDB調試Java程式其它注意事項

想要使用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++之間的調用慢得令人受不了。

上述的東西是催生出現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-. 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調試。

用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這個變量名。

  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 . 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) 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

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  <---條件斷點  

(gdb) p result

$2 = 1225

(gdb) n

result = 5050

Breakpoint 3, sum.Sum(int)long (n=1000) at sum.java:4                 <-----  函數斷點

(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

(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

Value returned is $1 = 499500

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調試被GCJ編譯的程式時,你需要讓GDB忽略SIGPWR和SIGCPU這兩個信号。這兩個信号被垃圾回收器使用,為了讓調試工作進行的更順利,我們需要使用GDB的指令來忽略這兩個信号:

(gdb) handle SIGPWR nostop noprint

Signal        Stop      Print   Pass to program De.ion

SIGPWR        No        No      Yes             Power fail/restart

(gdb) handle SIGXCPU nostop noprint

SIGXCPU       No        No      Yes             CPU time limit exceeded

當然,你并不用每次都需要設定這兩個指令,你可以設定$HOME目錄下的.gdbinit檔案來把這兩個指令作為GDB的初始化選項。

本文轉自 haoel 51CTO部落格,原文連結:http://blog.51cto.com/haoel/124573,如需轉載請自行聯系原作者