《HotSpot实战》—— 1.3 实战:在HotSpot内调试HelloWorld

本节书摘来异步社区《HotSpot实战》一书中的第1章,第1.3节,作者:陈涛,更多章节内容可以访问云栖社区“异步社区”公众号查看。

1.3 实战:在HotSpot内调试HelloWorld

本节讲解的是Java入门程序HelloWorld在HotSpot上的执行过程。我们通过一个普通Java程序的运行过程,能够以点带面地讲解到涉及HotSpot内部实现的基础概念。

虽然是调试简单的HelloWorld程序,但在这个过程中会涉及HotSpot的基本数据结构以及环境准备等内容。理解这些,一方面使读者对HotSpot项目有个感性认识,其实调试HotSpot没有想象的那么困难,这利于我们增强驾驭HotSpot的自信心;另一方面,让我们正式接触到HotSpot的基本代码,并掌握HotSpot项目的基本调试方法。

调试准备过程如图1-6所示,具体步骤如下。

(1)选择调试器。

(2)配置GDB工作目录的绝对路径。

(3)配置JDK和动态链接库路径。

(4)定位Launcher。

(5)运行GDB初始化脚本,准备GDB运行环境。

(6)设置HotSpot项目断点。

(7)启动调试脚本。

(8)虚拟机运行HelloWorld程序,在断点处暂停。

(9)利用GDB命令调试HotSpot虚拟机程序的运行。

《HotSpot实战》—— 1.3 实战:在HotSpot内调试HelloWorld

接下来,我们先了解一下如何使用GDB调试程序,然后开启我们的调试之旅。

1.3.1 认识GDB

本地程序(C/C++)的调试,一般使用GDB命令。对于Java程序员来说,GDB有些陌生,其实我们只需要掌握一些基本的调试命令,便足够应付HotSpot的调试任务了。

下面附上一些常用的GDB命令,包括断点、执行、查看代码、查看栈帧、查看数据等,如清单1-16所示。

清单1-16
断点:

break InitializeJVM:在InitializeJVM函数入口处设置断点
break java.c:JavaMain:在源文件java.c的InitializeJVM函数入口处设置断点
break os_linux.cpp:4380:在源文件os_linux.cpp的第4380行处设置断点
break *0x8048000:在地址为0x8048000的地址处设置断点
delete 1:删除断点1
delete:删除所有断点
执行:
step:执行1条语句,会进入函数
step n:执行n条语句,会进入函数
next:与step类似,但是不进入函数
next n:与step n类似,但是不进入函数
continue:继续运行
finish:运行至当前函数返回后退出
查看代码:
list n:查看当前源文件中第n行的代码
list InitializeJVM:查看InitializeJVM函数开始位置的代码
list:查看更多的行
list -:查看上次查看的代码行数之前的代码
默认,GDB打印10行。若需要调整,可使用:
set listsize n:调整打印行数为n行
查看栈帧:
frame n:从当前栈帧移动到#n栈帧
up n:从当前栈帧向上移动n个栈帧
down n:从当前栈帧向下移动n个栈帧
select-frame:查看更多的行
backstrace:查看整个调用栈
backstrace n:与backstrace类似,只不过只查看4个栈帧
backstrace full:查看整个调用栈,另外还打印出局部变量和参数
info args:查看函数参数
info locals:查看局部变量
查看数据:
print expr:查看expr的值,其中expr是源文件中的表达式
print /f expr n:以f指定的格式查看expr的值。其中f表示的格式可以为:
x:十六进制整数
d:有符号整数
u:无符号整数
o:八进制整数
t:二进制整数
c:字符常量
f:浮点数
s:字符串
r:原始格式
a:地址
x 0xbfffd034:查看内存地址为0xbfffd34的值
disassemble:查看汇编代码,反汇编当前函数
info registers:查看所有寄存器的值
print $eax:以十进制形式查看寄存器%eax的值
print /x $eax:以十六进制形式查看寄存器%eax的值

更多GDB的信息,可以参考GDB的官方教程1。

1.3.2 准备调试脚本

在HotSpot编译完成后,会在Jvmg目录下生成一个名为hotspot的脚本文件,如清单1-17所示。使用脚本可以替代大量重复性的输入,并且可以帮助我们准备好调试环境,为我们轻松调试系统创造了良好的环境。我们可以在此脚本文件的基础上调试HotSpot项目。

在启动调试之前,了解调试脚本究竟做了哪些工作是十分有益的,这有助于我们掌握独立分析和解决问题的能力,在出现问题时不致于手忙脚乱,可以利用自身所学知识解决问题。

清单1-17
来源:hotspot/src/os/posix/launcher/launcher.script
描述:调试脚本

#!/bin/bash```
首先是对传入的调试器名称参数进行转换,以便于定位到指定的调试器,支持的调试器包括GDB、GUD、DBX和VALGRIND等。

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”、“-gud”、“-dbx”或“-valgrind”。

# Make sure the paths are fully specified, i.e. they must begin with /.
SCRIPT=$(cd $(dirname $0) && pwd)/$(basename $0)
RUNDIR=$(pwd)

# 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```
${MYDIR}是配置脚本的绝对路径:

Find out the absolute path to this script

MYDIR=$(cd $(dirname $SCRIPT) && pwd)`
${JDK}用来配置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

# 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=i386

# Find out the absolute path to this script
MYDIR=$(cd $(dirname $SCRIPT) && pwd)

SBP=${MYDIR}:${JRE}/lib/${ARCH}

# 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

export LD_LIBRARY_PATH
export JAVA_HOME

JPARMS="$@ $JAVA_ARGS";```
${LAUNCHER}用作定位Launcher。关于Launcher,我们会在下一章中展开探讨。这里只需要知道它是虚拟机启动器程序便可:

Locate the gamma development launcher

LAUNCHER=${MYDIR}/gamma
if [ ! -x $LAUNCHER ] ; then

echo Error: Cannot find the gamma development launcher \"$LAUNCHER\"
exit 1

fi`
接下来是进行GDB自身初始化工作,包括配置工作路径以及信号等工作:

GDBSRCDIR=$MYDIR
BASEDIR=$(cd $MYDIR/../../.. && pwd)

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```
在这里,可以设置断点。选择你感兴趣的HotSpot项目源代码位置,如JVM初始化模块“InitializeJVM”函数入口。接下来,便可以利用GDB的break命令设置断点,如:

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
}`
剩余配置代码我们可以不做调整:

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```
至此,调试脚本已经准备就绪,接下来,让我们开始HotSpot的调试吧。输入命令:

sh hotspot –gdb HelloWorld`
启动调试,将出现如图1-7所示的界面。

《HotSpot实战》—— 1.3 实战:在HotSpot内调试HelloWorld

HotSpot运行在断点1(InitializeJVM)上停止下来,这时就可以利用前面提到的GDB命令尽情地控制HotSpot的运行了!

如果想让程序继续执行,输入continue命令使虚拟机正常运行下去,可以看到程序输出“Hello hotspot”并正常退出。感兴趣的读者可以亲自动手尝试一下。

建议读者结合源代码,利用GDB命令来跟踪调试HotSpot,查看系统运行时的内部数据和状态。这有两个好处:一方面,这能帮助我们将枯燥的阅读源码任务转换成有趣的虚拟机调试工作;另一方面,也能促进我们加深对HotSpot的理解。

上一篇:免费快递物流单号查询api接口对接指南(顺丰、中通、圆通、申通、韵达、百世)


下一篇:python设计模式(十二):结构型模式总结