📓 Archive

DEBUG

FGJ: Create:2024/01/19 Update: (2024-10-24)

  • debugger #

    • 参数解释 #

      indexexplain
      -Xdebug启用调试特性
      -Xrunjdwp在目标 VM 中加载 JDWP 实现。它通过传输和 JDWP 协议与独立的调试器应用程序通信。从 Java V5 开始,您可以使用 -agentlib:jdwp 选项,而不是 -Xdebug 和 -Xrunjdwp。
      -Djava.compiler=NONE禁止 JIT 编译器的加载
      transport传输方式,有 socket 和 shared memory 两种,我们通常使用 socket(套接字)传输,但是在 Windows 平台上也可以使用shared memory(共享内存)传输。
      server(y/n)VM 是否需要作为调试服务器执行
      address调试服务器的端口号,客户端用来连接服务器的端口号
      suspend(y/n)值是 y 或者 n,若为 y,启动时候自己程序的 VM 将会暂停(挂起),直到客户端进行连接,若为 n,自己程序的 VM 不会挂起
    • 参数样例 #

      Command line arguments for remote JVM.

      JDK版本(attach mode)启动追加命令[参考下图](listen mode)启动追加命令
      JDK9 or later-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:8800-agentlib:jdwp=transport=dt_socket,server=n,address=localhost:8800,suspend=y
      JDK 5-8-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8800-agentlib:jdwp=transport=dt_socket,server=n,address=localhost:8800,suspend=y
      JDK 1.4.x-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=8800-Xdebug -Xrunjdwp:transport=dt_socket,server=n,address=localhost:8800,suspend=y
      JDK 1.3.x or earliar-Xnoagent -Djava.compiler=NONE -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=8800-Xnoagent -Djava.compiler=NONE -Xdebug -Xrunjdwp:transport=dt_socket,server=n,address=localhost:8800,suspend=y

  • mode #

    debug mode:
    Attach: 此种模式下,server=y,address=port。被调试的代码端充当服务器开放端口等待jdi客户端去连接。
    Listen: 此种模式下,server=n,address=ip:port。应当先在ip机器上启动jdi客户端去监听端口port,然后启动需要被调试的代码。例如: IDEA debug按钮采用的方式

    需要注意的是:suspend=y/n. 指定是否被调试JVM需要被挂起。
       attach模式下: 如果调试简单application,则最好是suspend=y,这样的话不至于还没attach上targetVM,程序已经运行完了。如果是web应用,因为一直在运行。所以suspend=n也无妨。后续只要attach上targetVM就可以对targetVM进行控制。
       listen模式下: 感觉没有什么用。启动targetVM 的时候就要与指定ip:port的jdi客户端进行连接。比如:idea 设置远程debug 为listen模式。先启动监听本地localhost:8800.顺便打上断点。然后再启动需要被调试的JVM,启动的时候不管suspend=y/n,都会步入断点。

    • attach #

      • targetVM

        java -agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=8800 _draft.test.debug.HelloWorld 运行后会暂停,等待jdi客户端attach后才开始执行。

      • JDI client

      • Tomcat Debug #

        attach模式也可以参考 调试本地或远程的tomcat

    • listen #

      • targetVM

        java -agentlib:jdwp=transport=dt_socket,server=n,suspend=n,address=localhost:8800 _draft.test.debug.HelloWorld 等待下图jdi客户端监听开始之后再运行此命令。

      • JDI client

      • IDEA本地debug按钮(采用listen方式debug) #

        # 启动命令及参数
        /Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/bin/java 
            -agentlib:jdwp=transport=dt_socket,address=127.0.0.1:57982,suspend=y,server=n 
            -javaagent:/Users/stevenobelia/Library/Caches/JetBrains/IntelliJIdea2023.2/captureAgent/debugger-agent.jar 
            -Dfile.encoding=UTF-8 
            -classpath /Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/charsets.jar
                :/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/lib/tools.jar
                :/Applications/IntelliJ IDEA.app/Contents/lib/idea_rt.jar 
            _draft.test.debug.HelloWorld
        Connected to the target VM, address: '127.0.0.1:57982', transport: 'socket'
        
      • IDEA maven debug #

        /Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/bin/java 
            -agentlib:jdwp=transport=dt_socket,address=127.0.0.1:49219,suspend=y,server=n 
            -Dmaven.multiModuleProjectDirectory=/Users/stevenobelia/Documents/project_idea_test/idea-test-project 
            -Djansi.passthrough=true 
            -Dmaven.home=/Users/stevenobelia/software/apache-maven-3.6.0 
            -Dclassworlds.conf=/Users/stevenobelia/software/apache-maven-3.6.0/bin/m2.conf 
            -Dmaven.ext.class.path=/Applications/IntelliJ IDEA.app/Contents/plugins/maven/lib/maven-event-listener.jar 
        
            -javaagent:/Users/stevenobelia/Library/Caches/JetBrains/IntelliJIdea2023.2/captureAgent/debugger-agent.jar 
            -Dfile.encoding=UTF-8 
            -classpath /Users/stevenobelia/software/apache-maven-3.6.0/boot/plexus-classworlds-2.5.2.jar
                :/Applications/IntelliJ IDEA.app/Contents/lib/idea_rt.jar 
        
            org.codehaus.classworlds.Launcher 
            -Didea.version=2023.2.3 
            -s /Users/stevenobelia/software/apache-maven-3.6.0/conf/settings.xml 
            clean:clean
        
  • 问题 #

    使用idea运行JDI程序的时候会在捕获到VMStartEvent事件后,不会有ClassPrepareEvent,BreakpointEvent事件。
    当前使用的JDI环境为JDK17自带的。并非1.8中${JAVA_HOME}/lib/tools.jar中的类库。
    现象如下图(idea Run按钮运行后与命令行的方式运行出来的结果不同):

    • 原因: #

      Connector在launch的时候,会解析参数,并且生成 shell 执行指令数组。比如[/Library/Java/JavaVirtualMachines/jdk-17.0.2.jdk/Contents/Home/bin/java, -Xdebug, -Xrunjdwp:transport=dt_socket,address=localhost:63879,suspend=y, site.wtfu.framework.HelloWorld]。并通过Runtime.getRuntime().exec(commandArray)执行。 代码参考。重点在于,使用Runtime.getRuntime().exec()执行命令的时候,默认使用当前工程项目(比如本例中的idea-debug)为执行目录。而上面传递的参数中需要被targetVM执行的类site.wtfu.framework.HelloWorld在执行目录中没有,所以targetVM启动的时候报错了。也就直接退出了。

      如果需要验证,可以在编写的debugger程序中加入如下代码:使用process.getErrorStream()获取targetVM异常退出信息。

      public static void main(String[] args) {
          // ...
          try {
              vm = launchingConnector.launch(defaultArguments);
              // print trace message with: vm.setDebugTraceMode(4);
              process = vm.process();
      
              new Thread(()->{
                  BufferedReader reader = new BufferedReader(new InputStreamReader(process.getErrorStream()));
                  String line;
                  while (true) {
                      try {
                          if (!((line = reader.readLine()) != null)) break;
                      } catch (IOException e) { throw new RuntimeException(e); }
                      System.err.println(line);
                  }
              }).start();
          }catch(Exception e){e.printStackTrace();}
      }
      

      加入检测异常的代码后运行的到如下结果:

    • 解决 #

      1. 源代码

        修改AbstractLauncher.java源代码,利用Runtime.getRuntime().exec(String[] cmdarray, String[] envp, File dir)传入directory。显然不方便。

      2. 满足targetVM

        将编译好的HelloWorld.class 按照Runtime.getRuntime().exec(cmdarray)所需要的目录构造即可。如下图:

Reference #


comments powered by Disqus