`
bianku
  • 浏览: 69649 次
  • 性别: Icon_minigender_1
  • 来自: 常州
社区版块
存档分类
最新评论

用GDB 调试Java程序

    博客分类:
  • Java
阅读更多

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++之间的调用慢得令人受不了。

 

 

 

 

GNUJava编译器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

 

 

分享到:
评论

相关推荐

    使用eclipse/ndk-gdb对java/native code联合调试

    必很多网友都有这样的疑惑,我的程序中上层是java代码,下层使用c/c++ 写的,那到底要怎么调试呢? 我们首先想到的是ndk-...如果能将二者结合起来,一边用eclipse调试java代码,同时用ndk-gdb调试,那就再好不过了。

    使用gdb调试嵌入式系统

     Gdb可以调试各种程序,包括C、C++、JAVA、PASCAL、FORAN和一些其它的语言。包括GNU所支持的所有微处理器的汇编语言。 在gdb的所有可圈可点的特性中,有一点值得注意,就是当运行gdb的平台(宿主机)通过串行端口...

    GDB 调试工具指南

    目录: 1、准备工作 2、运行程序 3、单步执行 4、断点 5、查看变量 6、查看内存 7、查看源文件信息

    C编译 使用gdb调试1

    我们可以用function::variable的方式区分:(gdb) print mean::a 运行控制让程序从断点开始,再多运行一行:(gdb) step也

    C编译: 使用gdb调试

    它是一款UNIX平台的调试器(debugger),可用于为C, C++, Objective-C, Java, Fortran等程序debug。  在gdb中,你可以通过设置断点(break point)来控制程序运行的进度,并查看断点时的变量和函数调用状况,从而...

    windows下配置eclipse+CDT+Cygwin调试平台

    在Windows下用Eclipse+CDT+cygwin搭建GDB调试平台 以下软件均为Windows平台下的版本。 1、 安装JDK,目的是为了Eclipse的运行。 版本是jdk-1_5_0_06-windows-i586-p.exe或更高 ,下载地址...

    大内高手--调试手段及原理

    这里我不打算讲解如何使用boundschecker、purify、valgrind或者gdb,使用这些工具非常简单,讲解它们只是多此一举。相反,我们要研究一下这些工具的实现原理。 本文将从应用程序、编译器和调试器三个层次来讲解...

    gdb (GNU Debugger)使用手册

    Linux程序跟踪、调试用。与大家分享!

    java将文本转换成语音

    本工程为MyEclipse创建的java工程,用于探索如何将文本转换成语音。 本工程编码方式:UTF-8 参考http: 7个开源的TTS(文本转语音)系统:http://blog.csdn.net/gaohuanjie/article/details/22647159 3个开源TTS...

    dbg.vim:dbg.vim是vim scipt,用于调试程序。 (支持cdb,gdb,jdb,fdb,mdbg)

    支持的调试器: mdbg:.Net cdb:Windows C ++ gdb:GNU调试器jdb:Java调试器fdb:Flex调试器现在不支持pdb !! cdb是Windows的命令行调试器。 !! mdbg是.Net Framework的命令行调试器。要求dbg.vim是必需的vimproc...

    计算机课程设计:C++、java和Python介绍与学习技巧.docx

    - 学会使用调试工具,如gdb、valgrind等。 ## Java Java是一种广泛应用于企业级开发的高级编程语言,具有跨平台、安全、健壮等特点。学习Java需要掌握基本语法和面向对象的思想,了解JVM、多线程、网络编程等基础...

    Android C++高级编程: 使用NDK

    Android是移动电话市场的主要角色而且其市场份额正在持续增长。... 用logging、GDB和Eclipse调试器调试原生代码。  用Valgrind分析内存问题。  用GProf测试应用性能。  用SIMD/NEON优化原生代码。

    eCos使用经验总结

    在虚拟机下跑redboot 在虚拟机下跑redboot 这里有一个redboot.flp文件,是一个已经编译好的Vmware版的RedBoot,这是已经移植好的在vmware上运行...支持键盘、屏幕,网卡,串口,可以用i386-elf-gdb调试程序,无须硬件开

    Android C++高级编程使用NDK

    《移动开发经典丛书:Android C++高级编程:使用NDK》提供了移植、开发以及利用... 用logging、GDB和Eclipse调试器调试原生代码。  用Valgrind分析内存问题。  用GProf测试应用性能。  用SIMD/NEON优化原生代码。

    CGDB是GNU调试器的一个非常轻量级的控制台前端

    CGDB是GNU调试器的一个非常轻量级的控制台前端。它提供 一个分屏界面,显示下面的 GDB 会话和程序的 上面的源代码。该界面是以 vim 的为模型的,因此 vim 用户应该 使用它感觉宾至如归。

    java8看不到源码-KiwiApkProtect:移动Android应用程序的加密

    针对不同的应用安全需求,可以提供不同级别的安全防护功能,比如高强度的安全对抗,可以选择使用Java2C、代码混淆或者KiwiVM。 特点和优势 Dex Packer: Dex 文件的终极打包器,防止 dex2jar 提取 java 代码。 函数...

    eclipse 开发c/c++

    因此,对用于调试的 GDB 或用于编译的 GCC 和 Make 的依赖要求这些应用程序可用于用户想要使用的平台。 大多数 Linux(通常和类 POSIX)源代码软件包使用 autoconf 脚本来检查构建环境, 所以您必需运行 configure ...

    pro-android-c-w-ndk-master.tar

    《移动开发经典丛书:Android C++高级编程:使用NDK》提供了移植、开发以及利用... 用logging、GDB和Eclipse调试器调试原生代码。  用Valgrind分析内存问题。  用GProf测试应用性能。  用SIMD/NEON优化原生代码。

    嵌入式设计及linux驱动开发指南——基于ARM9处理器.pdf

    4.2.4 使用仿真器和ADS Debugger调试ARM开发板 4.3 JTAG接口 4.3.1 JTAG引脚定义 4.3.2 通过JTAG烧写Flash 4.3.3 烧写Flash技术内幕 第5章 Bootloader 5.1 嵌入式系统的引导代码 5.1.1 初识Bootloader 5.1.2...

    Vebugger:交互式 shell 调试器的前端

    - 步入、设置、设置和继续断点管理评估当前执行范围内的表达式弄乱程序的状态(改变值,调用函数) Vebugger 构建为用于为交互式 shell 调试器构建前端的通用框架,并附带以下实现: GDB - 不需要介绍... JDB - Java...

Global site tag (gtag.js) - Google Analytics