zoukankan      html  css  js  c++  java
  • Eclipse远程调试Tomcat

    最近,一直在研究Tomcat的工作内幕,主要的方法就是参考《How Tomcat Works》 这本书和Tomcat 5.5.26的源代码。

     

    Tomcat的代码结构还是比较清晰的,注释也比较全。但是代码毕竟是静态的,难以彻底弄清类与类之间的协作关系,以及运行时对象的交互关系。

     

    如果能对Tomcat的启动、处理请求和停止的过程进行断点调试,看清Tomcat的每一步行踪,那么就能解决上面的问题了。

     

    于是,又一个问题出来了:如何使用Eclipse远程调试Tomcat ?

     

    上网查了一些资料,相关的文章还是很多的。我简单梳理了一下解决方案及原理,顺便熟悉了Tomcat的启动脚本。

     

    如何远程调试JVM?

    远程调试Tomcat,本质上就是远程调试JVM。倒不是需要了解JVM自身的运行细节,而是要了解JVM上应用程序的运行细节。

    无论如何,我们都要获取JVM运行时的内部信息(比如查看调试信息),并对JVM的运行流程进行控制(比如单步执行),才能达到调试的目的。

     

    这个事情光靠调试器本身,肯定是做不到的。不然,JVM的安全性就大打折扣了。除非JVM提供某种“后门”,供调试器查询一些运行时信息,并允许调试器发送一些控制命令。

     

    不得不感慨,JVM的强大。从J2SE 1.4.2开始,就已经提出并实现了JavaTM Platform Debugger Architecture ,简称JPDA。

     

    JPDA简介

    顾名思义,JPDA为Java平台上的调试器定义了一个标准的体系结构。该体系结构包括3个主要组成部分:JVM TI、JDI和JDWP。

     

    JVM TI的全称是Java Virtual Machine Tool Interface,它定义了JVM为了支持调试而必须提供的功能及相应的访问接口。这些访问接口是以本地语言的形式提供的,由JVM(比如Sun公司的HotSpot VM)负责实现。

    不过,JVM TI只是JVM提供的一系列函数,调试器(特别是远程的调试器)如何调用呢?其实啊,JVM TI的直接客户端并不是调试器,而是一个称为“JPDA back-end”的东东。这个东东应该是属于JVM的一部分,在SUN JRE的bin目录下可以找到jdwp.dll(jdwp.so)的库文件,这就是JPDA back-end的实现。按我理解,JPDA back-end提供了各种访问方式(共享内存,Socket),通过这些方式接收调试器的请求,然后调用JVM TI接口。

     

    JDI的全称是Java Debug Interface,它定义了访问JVM TI接口的高层API,以纯Java语言提供,由JDK实现(在Sun JDK的tools.jar可以找到)。调试器直接使用JDI来实现调试的功能。与JPDA back-end相对应,JDI实现的角色就是JPDA front-end。

     

    JDWP的全称是Java Debug Wire Protocol,它定义了JPDA front-end和JPDA back-end之间通讯信息的二进制格式。这里的通讯信息主要包括两种:调试器发送给JVM的请求信息和JVM发送给调试器的调试信息。

     

    总结一下,调试器 调用JDK提供的JDI实现 (JPDA front-end),经由JDWP协议 ,和JVM自带的JPDA back-end (jdwp.dll, jdwp.so, ...)进行通讯。JPDA back-end 通过调用JVM TI接口 ,从而获知调试信息,或发送控制命令。然后,JPDA back-end 将调试信息或命令执行结果,通过JDWP协议 ,返回给调试器 。

     

    如何启用JPDA

    默认情况下,JVM并没有启用JPDA back-end。需要在启动JVM的命令行加载以下参数:

    -Xdebug -Xrunjdwp:transport=dt_socket, address=8000,server=y,suspend=y

     

    -Xdebug

    启用调试特性

    -Xrunjdwp

    启用JDWP实现,它包含若干子选项:

    transport=dt_socket

    JPDA front-end和back-end之间的传输方法。dt_socket表示使用套接字传输。

    address=8000

    JVM在8000端口上监听请求。

    server=y

    y表示启动的JVM是被调试者。如果为n,则表示启动的JVM是调试器。

    suspend=y

    y表示启动的JVM会暂停等待,直到调试器连接上。

     

    suspend=y这个选项很重要。如果你想从Tomcat启动的一开始就进行调试,那么就必须设置suspend=y。

     

    Tomcat的启动脚本

    只要Tomcat启动时,启用了JPDA,那么就可以被调试。而Tomcat默认是不启用JPDA的,需要我们手动开启。

     

    开启JPDA的方法也很简单,对Tomcat的启动脚本做点修改,把启动JPDA的命令行参数添加进去,就可以了。

     

    不过Tomcat启动脚本还是有点复杂的,并不是简单的”java ...“。我们先简单的梳理一下。

     

    在Tomcat 5.5.26发行版的bin目录下,有N多脚本,其中与Tomcat启停有关的脚本是(以Windows为例):

    startup.bat        //启动Tomcat

    shutdown.bat   //停止Tomcat

    catalina.bat       //包含启动/停止Tomcat的核心逻辑

     

    startup.bat和shutdown.bat都是通过调用catalina.bat来实现启动和停止的功能,因此他俩的代码都很少。主要的逻辑都在catalina.bat中。

     

    catalina.bat是个强大的脚本,通过参数,可以执行各种动作,包括debug run start stop version等。

    如果我们直接执行catalina.bat,就可以看到它的用法说明:

     

    直接执行catalina.bat时的输出

     

    startup.bat,其实就是执行catalina.bat start;shutdown.bat,其实就是执行catalina.bat stop。

    不难猜到,我们可以写一个jdpa.bat,直接调用catalina.bat jpda start,应该就可以启用JPDA。我们拷贝一份startup.bat,把下面这行

    call "%EXECUTABLE%" start %CMD_LINE_ARGS%

    修改成

    call "%EXECUTABLE%" jpda start %CMD_LINE_ARGS%

     

    不过,-Xdebug -Xrunjdwp:transport=dt_socket, address=8000,server=y,suspend=y,这些选项参数怎么传递给catalina.bat呢?

    看看catalina.bat前面的注释,server的值默认就是y,transport的值是变量JPDA_TRANSPORT,address的值是变量JPDA_ADDRESS,suspend的值是变量JPDA_SUSPEND 。如果没有显式地复制,这些变量的值默认是dt_shmem jdbconn n(默认值表示使用共享内存作为传输方式)。这些默认值不是我们需要的,Eclisep的远程调试目前只支持套接字传输。在调用catalina.bat jpda start之前,我们给这些变量设置恰当的值:

    set JPDA_TRANSPORT=dt_socket
    set JPDA_ADDRESS=8000
    set JPDA_SUSPEND=y

    call "%EXECUTABLE%" jpda start %CMD_LINE_ARGS%

     

    然后,直接执行jpda.bat,Tomcat就运行在JPDA可调式模式下:

    启用JPDA的Tomcat

     

    从上图可以看出,Tomcat的JPDA使用套接字传输,监听在8000端口。Tomcat的启动已经暂停,只有调试器连接上来,才会继续启动。

     

    我把jpda.bat的完整内容列在下面,其中黑体部分,在startup.bat基础上添加的代码:

     


     

    @echo off
    if "%OS%" == "Windows_NT" setlocal
    rem ---------------------------------------------------------------------------
    rem Jpda script for the CATALINA Server
    rem
    rem $Id: jpda.bat 302918 2004-05-27 18:25:11Z yoavs $
    rem ---------------------------------------------------------------------------

    rem Guess CATALINA_HOME if not defined
    set CURRENT_DIR=%cd%
    if not "%CATALINA_HOME%" == "" goto gotHome
    set CATALINA_HOME=%CURRENT_DIR%
    if exist "%CATALINA_HOME%/bin/catalina.bat" goto okHome
    cd ..
    set CATALINA_HOME=%cd%
    cd %CURRENT_DIR%
    :gotHome
    if exist "%CATALINA_HOME%/bin/catalina.bat" goto okHome
    echo The CATALINA_HOME environment variable is not defined correctly
    echo This environment variable is needed to run this program
    goto end
    :okHome

    set EXECUTABLE=%CATALINA_HOME%/bin/catalina.bat

    rem Check that target executable exists
    if exist "%EXECUTABLE%" goto okExec
    echo Cannot find %EXECUTABLE%
    echo This file is needed to run this program
    goto end
    :okExec

    rem Get remaining unshifted command line arguments and save them in the
    set CMD_LINE_ARGS=
    :setArgs
    if ""%1""=="""" goto doneSetArgs
    set CMD_LINE_ARGS=%CMD_LINE_ARGS% %1
    shift
    goto setArgs
    :doneSetArgs

    set JPDA_TRANSPORT=dt_socket
    set JPDA_ADDRESS=8000
    set JPDA_SUSPEND=y
     

    call "%EXECUTABLE%" jpda start %CMD_LINE_ARGS%

    :end

     


     

     

    在Eclipse中远程调试Tomcat

    首先将Tomcat 5.5.26的源代码分为container connectors jasper servletapi build五个项目,导入到Eclipse中。启动相关的代码主要在container中,就以它为当前项目,打开”Debug Configurations“对话框。

    然后创建一个”Remote Java Application“,Connection Type选择”Standard (Socket Attach)“,Host填写localhost(Tomcat所在的主机地址),Port填写8000。最后点击”Apply“保存。

    Eclipse的Debug Configurations对话框中配置远程调试

     

    首先确保已经执行了jpda.bat,Tomcat正在等待调试器连接;然后执行上述的Debug Configuration,Eclipse就可以连上Tomcat。

     

    Tomcat的启动是从Bootstrap的main方法开始,我在第一行代码处设置了断点,Tomcat的启动就停在了这一行:

     

    断点调试Tomcat的启动过程

     

    接着,让Tomcat继续执行,我们可以看到,控制台输出了启动信息。

    Tomcat在JPDA模式下继续启动

  • 相关阅读:
    【Leetcode】【Easy】Remove Duplicates from Sorted List
    【Leetcode】【Easy】Pascal's Triangle II
    【Leetcode】【Easy】Pascal's Triangle
    【Leetcode】【Easy】Binary Tree Level Order Traversal II
    【Leetcode】【Easy】Binary Tree Level Order Traversal
    【Leetcode】【Easy】Maximum Depth of Binary Tree
    【Leetcode】【Easy】Minimum Depth of Binary Tree
    【Leetcode】【Easy】Balanced Binary Tree
    【Leetcode】【Easy】Symmetric Tree
    如何使用Action.Invoke()触发一个Storyboard
  • 原文地址:https://www.cnblogs.com/yangkai-cn/p/4016656.html
Copyright © 2011-2022 走看看