陳皓html
http://blog.csdn.net/haoeljava
想要使用GDB調試程序,就須要用GNU的編譯器編譯程序。如:用GCC編譯的C/C++的程序,才能用GDB調試。對於Java程序也是同樣的,若是想要用GDB調試,那麼就須要用GNU的Java編譯器——GCJ來編譯Java程序。linux
目前,不少Linux都不會預裝Sun的JVM,取而代之是使用GNU的開源編譯器來編譯和運行Java程序。好比RedHat和Ubuntu,其默認安裝都是使用GNU的Java編譯器(gcj)和解釋器(gij)。固然,它們都被腳本javac和java包裝了起來,你一不當心還覺得是使用了Sun的JVM。c++
爲何GNU要搞出一個Java的編譯和解釋器來呢?其大體有如下幾點:express
a) 傳統的JVM太慢了,由於它解釋的是class文件中的bytecode。這種方法實在是太慢了。
ubuntu
b) 爲了優化性能,引入了JIT(Just-In-Time),JIT會分析代碼,找出那些被反覆調用到必定次數的方法和函數,而後直接把這個方法直接處理成彙編machine code,之後就直接運行機器碼了。
函數
c) 固然,JIT也有問題,一個是startup overhead,就是說啓動的時候有點過度了,表現爲時間慢,而且,每次編譯後,都須要JIT從新作來過。另外一個問題是JIT比較耗費空間。
oop
d) 傳統的java還有一個比較扯的問題,就是佈署起來太麻煩了,須要有N個jar文件,而不是一個可執行文件。而且,Java須要一個很肥大的運行環境。另外,在java和c/c++之間的調用慢得使人受不了。post
上述的東西是催生出現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程序很簡單,關於編譯成.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程序做爲演示程序。
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