解析
本文是基于jdk1.6进行解析的,在HotSpot中存在两种启动器,一种是通用启动器(java/javaw),另一种是调试版启动器(gamma).
在对openjdk编译后,会在jvmg 目录下生成hotSpot脚本,这个启动器入口位于hotspot/src/share/tools/luncher/java.c. 本文就来看一下该脚本
#解析
该脚本中首先是设置gdb,dbx,valgrind,emacs等参数.如下:
#
# User changeable parameters ------------------------------------------------
#
# This is the name of the gdb binary to use
if [ ! "$GDB" ]
then
GDB=gdb
fi
# This is the name of the gdb binary to use
if [ ! "$DBX" ]
then
DBX=dbx
fi
# This is the name of the Valgrind binary to use
if [ ! "$VALGRIND" ]
then
VALGRIND=valgrind
fi
# This is the name of Emacs for running GUD
EMACS=emacs
其中,gdb是linux环境中的调试工具,可通过官方文档进行了解,dbx是uninx中的调试工具,可通过该文档进行了解, valgrind是一款用于内存调试、内存泄漏检测以及性能分析的软件开发工具。emacs则是著名的集成开发环境和文本编辑器。Emacs被公认为是最受专业程序员喜爱的代码编辑器之一,另外一个是vim。
接下来时获得当前脚本中和当前脚本所在的目录:
# Make sure the paths are fully specified, i.e. they must begin with /.
SCRIPT=$(cd $(dirname $0) && pwd)/$(basename $0)
RUNDIR=$(pwd)
这里针对$dirname $0等做一些说明:
变量 | 含义 |
---|---|
$0 | 当前脚本的文件名 |
$n | 传递给脚本或函数的参数。n 是一个数字,表示第几个参数。例如,第一个参数是$1,第二个参数是$2。 |
$# | 传递给脚本或函数的参数个数。 |
$* | 传递给脚本或函数的所有参数。 |
$@ | 传递给脚本或函数的所有参数。被双引号(" ")包含时,与 $* 稍有不同,下面将会讲到。 |
$? | 上个命令的退出状态,或函数的返回值。 |
$$ | 当前Shell进程ID。对于 Shell 脚本,就是这些脚本所在的进程ID。 |
$dirname | 取指定路径所在的目录 |
$basename | 去除目录后剩下的名字 |
关于这部分的内容可以参考如下链接:
Shell特殊变量:Shell $0, $#, $*, $@, $?, $$和命令行参数
接下来,匹配运行模式,默认情况下是run,如下:
# Look whether the user wants to run inside gdb
case "$1" in
-gdb)
MODE=gdb
shift
;;
-gud)
MODE=gud
shift
;;
-dbx)
MODE=dbx
shift
;;
-valgrind)
MODE=valgrind
shift
;;
*)
MODE=run
;;
esac
接下来,是获得该脚本的目录的绝对路径
#Find out the absolute path to this script
MYDIR=$(cd $(dirname $SCRIPT) && pwd)
接下来时设置jdk:
JDK=
if [ "${ALT_JAVA_HOME}" = "" ]; then
source ${MYDIR}/jdkpath.sh
else
JDK=${ALT_JAVA_HOME%%/jre};
fi
if [ "${JDK}" = "" ]; then
echo Failed to find JDK. ALT_JAVA_HOME is not set or ./jdkpath.sh is empty or not found.
exit 1
fi
如果没有设置ALT_JAVA_HOME环境变量的话,则执行${MYDIR}/jdkpath.sh 设置环境变量,否则,jdk指向ALT_JAVA_HOME/jre. 其中,jdkpath.sh的内容如下:
# Generated by /usr/local/openjdk-6/hotspot/make/linux/makefiles/buildtree.make
JDK=/usr/java/jdk1.6.0_45
该文件中的内容依赖于你本机中编译的情况
接下来设置JRE,JAVA,ARCH,MYDIR,SBP等变量:
# We will set the LD_LIBRARY_PATH as follows:
# o $JVMPATH (directory portion only)
# o $JRE/lib/$ARCH
# followed by the user's previous effective LD_LIBRARY_PATH, if
# any.
JRE=$JDK/jre
JAVA_HOME=$JDK
ARCH=@@LIBARCH@@
# Find out the absolute path to this script
MYDIR=$(cd $(dirname $SCRIPT) && pwd)
SBP=${MYDIR}:${JRE}/lib/${ARCH}
设置LD_LIBRARY_PATH:
# Set up a suitable LD_LIBRARY_PATH
if [ -z "$LD_LIBRARY_PATH" ]
then
LD_LIBRARY_PATH="$SBP"
else
LD_LIBRARY_PATH="$SBP:$LD_LIBRARY_PATH"
fi
接下来,设置JAVA_HOME, LD_LIBRARY_PATH等环境变量:
export LD_LIBRARY_PATH
export JAVA_HOME
设置参数:
JPARMS="$@ $JAVA_ARGS";
因此,我们可以通过JAVA_ARGS环境变量来进行设置参数
获取gamma调试器:
# Locate the gamma development launcher
LAUNCHER=${MYDIR}/gamma
if [ ! -x $LAUNCHER ] ; then
echo Error: Cannot find the gamma development launcher \"$LAUNCHER\"
exit 1
fi
设置GDBSRCDIR, BASEDIR等变量:
GDBSRCDIR=$MYDIR
BASEDIR=$(cd $MYDIR/../../.. && pwd)
声明了init_gdb函数,该函数会在gdb模式下用到:
init_gdb() {
# Create a gdb script in case we should run inside gdb
GDBSCR=/tmp/hsl.$$
rm -f $GDBSCR
cat >>$GDBSCR <<EOF
cd `pwd`
handle SIGUSR1 nostop noprint
handle SIGUSR2 nostop noprint
set args $JPARMS
file $LAUNCHER
directory $GDBSRCDIR
# Get us to a point where we can set breakpoints in libjvm.so
break InitializeJVM
run
# Stop in InitializeJVM
delete 1
# We can now set breakpoints wherever we like
EOF
}
这里做些解释:
-
cat >>$GDBSCR <<EOF --> 是创建/tmp/hsl.当前Shell进程ID 的文件,其内容是EOF之前的内容.
-
handle --> 在GDB中定义一个信号处理。信号可以以SIG开头或不以 SIG开头,可以用定义一个要处理信号的范围(如:SIGIO-SIGKILL,表示处理从SIGIO信号到SIGKILL的信号,其中包括SIGIO, SIGIOT,SIGKILL三个信号),也可以使用关键字all来标明要处理所有的信号。一旦被调试的程序接收到信号,运行程序马上会被GDB停住,以 供调试。
-
SIGUSR1/SIGUSR2 --> 用户自定义信号,关于这个,可以参考如下链接:
-
nostop --> 当被调试的程序收到信号时,GDB不会停住程序的运行,但会打出消息告诉你收到这种信号。
-
noprint -->当被调试的程序收到信号时,GDB不会告诉你收到信号的信息。
-
set args --> 指定运行时参数。
-
file --> 设置要执行的程序
-
directory --> 设置源文件的目录
-
break InitializeJVM --> 在InitializeJVM函数入口处设置断点
-
delete 1 --> 删除断点1
关于gdb的使用可以参数如下链接:
接下来,就会针对不同的模式进行处理:
case "$MODE" in
gdb)
init_gdb
$GDB -x $GDBSCR
rm -f $GDBSCR
;;
gud)
init_gdb
# First find out what emacs version we're using, so that we can
# use the new pretty GDB mode if emacs -version >= 22.1
case $($EMACS -version 2> /dev/null) in
*GNU\ Emacs\ 2[23]*)
emacs_gud_cmd="gdba"
emacs_gud_args="--annotate=3"
;;
*)
emacs_gud_cmd="gdb"
emacs_gud_args=
;;
esac
$EMACS --eval "($emacs_gud_cmd \"$GDB $emacs_gud_args -x $GDBSCR\")";
rm -f $GDBSCR
;;
dbx)
$DBX -s $MYDIR/.dbxrc $LAUNCHER $JPARAMS
;;
valgrind)
echo Warning: Defaulting to 16Mb heap to make Valgrind run faster, use -Xmx for larger heap
echo
$VALGRIND --tool=memcheck --leak-check=yes --num-callers=50 $LAUNCHER -Xmx16m $JPARMS
;;
run)
LD_PRELOAD=$PRELOADING exec $LAUNCHER $JPARMS
;;
*)
echo Error: Internal error, unknown launch mode \"$MODE\"
exit 1
;;
esac
RETVAL=$?
exit $RETVAL
如果是gdb模式的话,则会创建/tmp/hsl.当前Shell进程ID 的文件,然后执行 gdb -x $GDBSCR 命令进行调试,调试结束后,删除临时文件.
这里对–annotate=3(emacs中的gdb参数)做如下解释:
- annotate = 0是最基本的模式和在命令行使用gdb完全一样
- annotate = 1是单步调试模式,出现上下两个窗口,上面是gdb运行的buffer,下面是你代码的buffer,会在代码 buffer中,同步指示当前运行的语句的位置.
- annotate = 2是产生注解的模式。
- annotate = 3是信息最完整的模式。此时的 Emacs 分5个 buffer,从上到下、从左到右依次是:gdb 调试窗口、变量实时变化显示窗口、源代码窗口、栈窗口、断点信息.
关于这部分内容,可以参考: