IDEA 远程 DEBUG
2024-09-18 02:46:04 # Technical # Notes

最近遇到一个离谱的 bug,本地运行 ok,但部署到测试环境后就无任何响应,也无任何异常。于是想通过远程 DEBUG,看下测试环境中执行情况

原理简述

Java Debug Wire Protocol (JDWP)

  • JDWP 是 Java 平台调试架构(Java Platform Debug Architecture,JPDA)的一部分。JDWP 是调试器与被调试的 Java 虚拟机(JVM)之间进行通信的协议

  • 被调试的 JVM 运行时会开启一个调试端口,调试器通过这个端口与 JVM 进行通信,发送调试命令并接收调试信息

Java Debug Interface (JDI)

  • JDI 是 JPDA 的一部分,提供了一个高层次的 Java 接口,用于开发调试工具。IDEA 的调试器使用 JDI 与 JVM 进行交互
  • 通过 JDI,IDEA 可以发送各种调试命令,例如设置断点、控制线程执行(如单步执行)、查看和修改变量值等

JVMTI(JVMDI)

  • JVMTI(虚拟机调试接口)在 jdk1.4 之前被称为 JVMDI,之后改名为 JVMTI,它是虚拟机的本地接口,其相当于 Thread 的 sleep、yield native 方法
  • JVMTI 是 JPDA 的另一部分,位于 JVM 内部,提供底层的调试功能
  • JVMTI 允许调试器插入断点、捕捉事件(如异常、线程启动或结束等)、控制线程执行等

IDEA 本地 DEBUG 过程

  1. 启动调试会话
    • IDEA 在启动应用程序时,可以选择以调试模式启动,此时,IDEA 会指定 JVM 参数来启用 JDWP,并指定调试端口
    • JVM 启动后,监听指定端口,等待调试器连接
  2. 连接到目标 JVM
    • IDEA 通过 JDWP 连接到目标 JVM,连接成功后,IDEA 开始与 JVM 进行通信
  3. 设置断点
    • 用户在 IDEA 中设置断点,IDEA 将这些断点通过 JDI 发送给 JVM,JVM 会记录下这些断点位置
    • 当 JVM 执行到断点位置时,会暂停执行,并通知 IDEA
  4. 控制执行流
    • IDEA 提供各种控制执行流的命令,例如单步执行、继续执行、跳过等。这些命令通过 JDI 发送给 JVM,JVM 根据命令执行相应的操作
    • 用户可以在 IDEA 中查看变量的当前值,监视表达式,甚至修改变量值
  5. 捕捉事件
    • JVM 会将各种调试的相关事件(如异常抛出、线程变化等)通知给 IDEA,IDEA 将这些事件展示给用户
  6. 调试信息展示
    • IDEA 通过 JDI 获取目标的 JVM 调试信息,包括线程状态、堆栈信息、变量值等,并在调试界面中展示给用户

简单说来就是 IDEA 启用 JVM 的调试模式,然后连接上 JVM

通过控制台上的日志可以窥见一二

1
2
"C:\Program Files\Java\jdk1.8.0_181\bin\java.exe" -agentlib:jdwp=transport=dt_socket,address=127.0.0.1:63731,suspend=y,server=n ......
已连接到地址为 ''127.0.0.1:63731',传输: '套接字'' 的目标虚拟机

JDWP 命令

所以关键就是 -agentlib:jdwp=transport=dt_socket,address=127.0.0.1:63731,suspend=y,server=n 这行 JVM 配置

  • -agentlib:jdwp:加载 JDWP 代理库
  • transport=dt_socket:指定调试器与 JVM 的通信方式。dt_socket 表示使用 TCP/IP 套接字进行通信
  • address=127.0.0.1:63731:指定调试器连接的地址和端口
  • suspend=y:指定 JVM 在启动后是否应该立即暂停。suspend=y 表示 JVM 在启动后会暂停,等待调试器连接。这种方式允许调试器在应用程序的第一行代码执行之前进行连接和设置断点
  • server=n:指定 JVM 是以客户端模式还是服务器模式运行调试会话。server=n 表示 JVM 以客户端模式运行,JVM 将尝试连接到指定的调试器。如果设置为 server=y,则表示 JVM 以服务器模式运行,等待调试器连接到它

实操

  1. 修改启动命令

    -agentlib:jdwp=transport=dt_socket,address=<ip>:<port>,suspend=y,server=n ip 改为远程服务的 ip,port 可以自定义一个不冲突的 port

  2. 如果是在容器中执行的,将上面的端口映射容器外

  3. IDEA 添加远程 JVM 调试

    指定上面配置的ip 和 port,然后选择对应的模块即可

远程 JVM 调试

PS:没有源码

如果是 docker 容器部署的,可以通过 docker ps --no-trunc 打印出不截短的容器描述,从中找出启动的命令,然后将容器内的 jar 拷贝出来

用 idea 创建一个空项目,然后将 jar 复制到空项目的目录下,然后 右键 -> 添加为库

拷贝出来的 jar 包可能会有两种,一种有 BOOT-INF 目录,一种没有

BOOT-INF

BOOT-INF 文件夹的话就需要打开模块设置,添加 BOOT-INF 文件夹

Project Structure

如果这个 jar 包还依赖其他 jar 包,可以将 lib 下的依赖复制到根目录然后 添加为库