博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
用GDB 调试Java程序
阅读量:5807 次
发布时间:2019-06-18

本文共 6300 字,大约阅读时间需要 21 分钟。

背景

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

GNUJava编译器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-. 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
的使用,请参看我的另一篇文章《
》。
 
 

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程序

 
如同我的《
》一文,我使用如下的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 . 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) 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 De.ion
SIGPWR        No        No      Yes             Power fail/restart
(gdb) handle SIGXCPU nostop noprint
Signal        Stop      Print   Pass to program De.ion
SIGXCPU       No        No      Yes             CPU time limit exceeded
 
当然,你并不用每次都需要设置这两个命令,你可以设置$HOME
目录下的.gdbinit
文件来把这两个命令作为GDB
的初始化选项。
本文转自 haoel 51CTO博客,原文链接:http://blog.51cto.com/haoel/124573,如需转载请自行联系原作者
你可能感兴趣的文章
Valid Parentheses
查看>>
【ES6】数值的扩展
查看>>
性能测试之稳定性测试
查看>>
ES6的 Iterator 遍历器
查看>>
2019届高二(下)半期考试题(文科)
查看>>
【REDO】删除REDO LOG重做日志组后需要手工删除对应的日志文件(转)
查看>>
nginx 301跳转到带www域名方法rewrite(转)
查看>>
AIX 配置vncserver
查看>>
windows下Python 3.x图形图像处理库PIL的安装
查看>>
【IL】IL生成exe的方法
查看>>
network
查看>>
SettingsNotePad++
查看>>
centos7安装cacti-1.0
查看>>
3个概念,入门 Vue 组件开发
查看>>
没有JS的前端:体积更小、速度更快!
查看>>
数据指标/表现度量系统(Performance Measurement System)综述
查看>>
GitHub宣布推出Electron 1.0和Devtron,并将提供无限制的私有代码库
查看>>
Angular2, NativeScript 和 React Native比较[翻译]
查看>>
论模式在领域驱动设计中的重要性
查看>>
国内首例:飞步无人卡车携手中国邮政、德邦投入日常运营
查看>>