用GDB 調試Java程序


 

 

陳皓

http://blog.csdn.net/haoel

 

背景

 

想要使用GDB調試程序,就需要用GNU的編譯器編譯程序。如:用GCC編譯的C/C++的程序,才能用GDB調試。對於Java程序也是一樣的,如果想要用GDB調試,那么就需要用GNUJava編譯器——GCJ來編譯Java程序。

 

目前,很多Linux都不會預裝SunJVM,取而代之是使用GNU的開源編譯器來編譯和運行Java程序。比如RedHatUbuntu,其默認安裝都是使用GNUJava編譯器(gcj)和解釋器(gij)。當然,它們都被腳本javacjava包裝了起來,你一不小心還以為是使用了SunJVM

 

為什么GNU要搞出一個Java的編譯和解釋器來呢?其大致有以下幾點:

 

a)      傳統的JVM太慢了,因為它解釋的是class文件中的bytecode。這種方法實在是太慢了。

b)      為了優化性能,引入了JITJust-In-Time),JIT會分析代碼,找出那些被反復調用到一定次數的方法和函數,然后直接把這個方法直接處理成匯編machine code,以后就直接運行機器碼了。

c)      當然,JIT也有問題,一個是startup overhead,就是說啟動的時候有點過分了,表現為時間慢,並且,每次編譯后,都需要JIT重新做來過。另一個問題是JIT比較耗費空間。

d)      傳統的java還有一個比較扯的問題,就是布署起來太麻煩了,需要有Njar文件,而不是一個可執行文件。並且,Java需要一個很肥大的運行環境。另外,在javac/c++之間的調用慢得令人受不了。

 

 

GNU的Java編譯器GCJ

 

上述的東西是催生出現gcj的原因,GNU用了Ahead-of-Time Compilation來形容GCJGNUGCJ的出現在理由做了下面的說明:

 

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來說,它可以裁剪和做適時優化,因為是在運行時。 SunHotSpot技術是其中比較牛的技術,但gcj的技術也不一定就比JIT差。

 

d)      對於使用gcj的人來說,最大的一個好處就是startup speed和內存空間使用率。啟動JVMJIT會肖耗很大的內存,例如:NetBean啟動就需要74M的內存(什么事也沒有干), JEmacs使用Swing,一啟動就是26M,而XEmacs只有8M(這些數據是比較老的了,大約在2003年的數據)。

 

e)       GCJ剛出道時,有人比較了Kawa Test SuiteGCJJDK1.3.1下的運行比較。結果是,GCJ速度比SunJIT快兩倍,因為GCJSunJDK少了一半以上的內存訪問未命中的事情,也就是說少了一半的內存換頁。並且,實際運行過程中,也少了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

 

目前我不知道是否是GDBbug,不過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忽略SIGPWRSIGCPU這兩個信號。這兩個信號被垃圾回收器使用,為了讓調試工作進行的更順利,我們需要使用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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM