用GDB 调试Java程序

陈皓

http://blog.csdn.net/haoel

背景

想要使用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选项)

[email protected]:~/java$ gcj --main=sum -g -o sum sum.java

进入GDB环境:

[email protected]:~/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 ([email protected]) 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 ([email protected]) 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 ([email protected]) 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 ([email protected]) 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

时间: 2024-10-19 23:33:39

用GDB 调试Java程序的相关文章

gdb调试C程序

gdb调试 概述 GDB是GNU开源组织发布的一个强大的UNIX下的程序调试工具.或许,各位比较喜欢那种图形界面方式的,像VC.BCB等IDE的调试,但如果你是在UNIX平台下做软件,你会发现GDB这个调试工具有比VC.BCB的图形化调试器更强大的功能.所谓“寸有所长,尺有所短”就是这个道理. 一般来说,GDB主要帮忙你完成下面四个方面的功能: 1) 启动你的程序,可以按照你的自定义的要求随心所欲的运行程序. 2) 可让被调试的程序在你所指定的调置的断点处停住.(断点可以是条件表达式) 3) 当

如何使用加多宝(jdb)在linux下调试Java程序

毕业时写了一段时间的C,那时候调试使用gdb,后来转了java,当时就想java程序怎么调试,找了一下,果然,那就是jdk自带的jdb windows里是这样的 Linux下是这样的 一般我在linux下来调试Java程序 好,那么,问题来了,这玩意怎么用?有好几种玩法 第一种玩法:以经典的HelloWorld为例,先写一个Java程序,如下: 我们把编译好的类上传到linux下,如下所示 务必强调一下:类所在的包名的层次结构也要在linux下体现出来 下面我们来玩起来,在linux下操作如下

使用 Eclipse 调试 Java 程序的技巧【9】

若有不正之处,请多多谅解并欢迎批评指正,不甚感激.请尊重作者劳动成果: 本文原创作者:pipi-changing本文原创出处:http://www.cnblogs.com/pipi-changing/ 本文版权归作者和博客园共有,未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接 ,否则保留追究法律责任的权利. 使用 Eclipse 调试 Java 程序的技巧 不要调试太多 只调试你觉得有问题的代码或者一部分一部分功能的调试: [ 断点视图 : 条件断点] 如果你只对应用中的某部分感

【Eclipse】eclipse调试java程序的九个技巧

本文转自[半夜乱弹琴],原文地址:http://www.cnblogs.com/lingiu/p/3802391.html 九个技巧: 逻辑结构 条件debug 异常断点 单步过滤 跳到帧 Inspect expressions display 远程debug 最早开始用eclipse的debug的时候,只会F5 F6 F7 F8,甚至F7都不是很搞的明白是怎么用的,那时候资浅,碰不到需要复杂debug的代码,慢慢工作深入了,场景碰多了,就需要各种debug技巧来提升定位bug效率,以前找人帮忙

使用 GDB 调试多进程程序

GDB 是 linux 系统上常用的调试工具,本文介绍了使用 GDB 调试多进程程序的几种方法,并对各种方法进行比较. 3 评论 田 强 ([email protected]), 软件工程师, IBM中国软件开发中心 2007 年 7 月 30 日 内容 在 IBM Bluemix 云平台上开发并部署您的下一个应用. 开始您的试用 GDB 是 linux 系统上常用的 c/c++ 调试工具,功能十分强大.对于较为复杂的系统,比如多进程系统,如何使用 GDB 调试呢?考虑下面这个三进程系统: 进程

使用Eclipse调试Java 程序的10个技巧

你应该看过一些如<关于调试的N件事>这类很流行的帖子 .假设我每天花费1小时在调试我的应用程序上的话,那累积起来的话也是很大量的时间.由于这个原因,用这些时间来重视并了解所有使我们调试更方便的功能. 那能为你省下一些时间,也将会使你的生活更安逸.轻松.同时也表明其它关于此主题的帖子也是很有价值的. 第1条:不要调试太多 一个关于调试的疯狂声明作为开头.但它必须是要说的!尝试切分一下你那复杂的逻辑成多个独立的单元,并编写单元测试来检测你代码的正确性.我想像如下 这样的流程应该是发生得非常频繁的-

使用gdb调试多线程程序总结

转:使用gdb调试多线程程序总结 一直对GDB多线程调试接触不多,最近因为工作有了一些接触,简单作点记录吧. 先介绍一下GDB多线程调试的基本命令. info threads 显示当前可调试的所有线程,每个线程会有一个GDB为其分配的ID,后面操作线程的时候会用到这个ID. 前面有*的是当前调试的线程. thread ID 切换当前调试的线程为指定ID的线程. break thread_test.c:123 thread all在所有线程中相应的行上设置断点thread apply ID1 ID

用 gdb 调试 GCC 程序【转】

用 GDB 调试程序 原著:Rick McMullin 用 gdb 调试 GCC 程序 转自:http://blog.csdn.net/bonnshore/article/details/7955422 Linux 包含了一个叫 gdb 的 GNU 调试程序. gdb 是一个用来调试 C 和 C++ 程序的强力调试器. 它使你能在程序运行时观察程序的内部结构和内存的使用情况. 以下是 gdb 所提供的一些功能: 它使你能监视你程序中变量的值. 它使你能设置断点以使程序在指定的代码行上停止执行.

linux下如何用GDB调试c++程序

原文地址:http://blog.csdn.net/wfdtxz/article/details/7368357 GDB 是GNU开源组织发布的一个强大的UNIX下的程序调试工具.或许,各位比较喜欢那种图形界面方式的,像VC.BCB等IDE的调试,但如果你是在 UNIX平台下做软件,你会发现GDB这个调试工具有比VC.BCB的图形化调试器更强大的功能.所谓“寸有所长,尺有所短”就是这个道理. 一般来说,GDB主要帮忙你完成下面四个方面的功能: 启动你的程序,可以按照你的自定义的要求随心所欲的运行