Java调试原理初探

2023-11-12

对于所有程序员,程序调试是一项必备的技能。在java程序中,最简单的就是通过 System.out.println()来打印输出各种变量来发现问题,而用的最多的莫过于通过各种调试器来进行调试,如图一所示的eclipse调试器,甚至还可以进行远程调试。对于这些调试器是如何实现的,这就需要了解本文的重点——JPDA(Java Platform Debugger Architecture)Java平台调试体系结构。

图1 eclipse调试器

 

JPDA体系概述

总所周知Java程序是运行在JVM上的,调试java程序,事实上就是向虚拟机请求其当前的运行状态等,JPDA就是虚拟机提供的一整套用于java调试的工具和接口。

JPDA可以分为三个部分,分别是Java虚拟机工具接口(JVMTI),Java调试线协议(JDWP)以及Java调试接口(JDI)。这三个层次把调试过程分解成三个不同的概念:调试者(debugger)和被调试者(Target JVM),以及它们的交互通道。图2展示了三者的相互关系。

图2 JPDA层次关系

 

JVMTI概述及使用

JVMTI其实就是一套由虚拟机直接提供的本地代码接口,包含了调试、监听、线程分析及覆盖率分析等接口(接口定义可以参考jdk中的jvmti.h文件),它处于整个JPDA体系的最底层。基于JVMTI这些强大的接口,可以实现java调试器、java运行态测试以及分析工具等。对于主流的java虚拟机,都有提供标准的JVMTI实现,实现过程比较复杂,这里就不赘述它的实现。

一般我们可以通过采用建立一个Agent的方式来使用JVMTI,其显著的特征就是通过设置回调函数的方式,从java虚拟机上得到当前运行态信息,并做出自己的相应的操作,抑或操作虚拟机的运行态,以达到一些特定的目的(如优化程序性能)。把Agent编译成一个动态链接库后,就可以在java程序启动的时候(增加启动参数agentlib/ agentpath)来加载它(java5之后可以使用运行时加载)。

以启动时加载为例,动态库加载后,虚拟机会寻找Agent的入口函数,定义如下:

在该函数中,虚拟机传入了一个JavaVM指针和命令行参数options,由javaVM,可以获得jvmtiEnv指针,而通过 jvmtiEnv可以获取所有的JVMTI函数。通过options可以做一些初始化操作(如初始化连接方式等)。

假如我们需要某个类的字节码文件读取之后,类定义之前能修改相关的字节码,从而使创建的class对象是我们修改之后的字节码内容,就需要写一个HandleClassFileLoadHook函数,然后在Agent_OnLoad函数里为jvmtiEventCallbacks设定对应的函数指针。

这样在接下来的虚拟机运行中,所有类的字节码文件在读取时都会进入HandleClassFileLoadHook函数,可以在HandleClassFileLoadHook函数中进行修改字节码等操作。

 

JDWP协议

JDWP(Java Debug Wire Protocol)定义了调试者和被调试者之间的通讯协议。在java程序中,调试者和被调试者运行在各自的java虚拟机上,被调试者(Target JVM)在启动时会加载一个Agent,该Agent里实现了各种方法,使用JVMTI函数,从而具备了调试功能,调试者(Debugger)与被调试者(Target JVM)建立连接后,向其发送命令来获取其运行时的状态和控制java程序的运行,发送命令和获取应答用的就是JDWP协议。

JDWP通讯可以分为两个阶段:握手和应答。握手是在传输层建立连接后做的第一件事,Debugger发送字符串“JDWP-Handshake”到Target JVM,后者同样回复“JDWP-Handshake”则表示握手完成,通信正常。握手完成后,Debugger就可以向Target JVM发送命令了。

 

Packet结构

由上面可以知道JDWP是通过command和reply进行通信,因此对应两种packet类型:命令包(command packet)和回复包(reply packet),Packet分为包头(header)和数据(data)两部分组成。包头部分的结构和长度是固定的,而数据部分的长度是可变的。command packet和reply packet的包头长度相同,都是11个bytes。

command packet包头结构如图三所示,Length 表示整个packet的长度(含 length 本身)。Id是一个唯一值,用来标记和识别reply所属的command。Reply packet与它对应的command packet具有相同的Id,异步的消息就是通过Id来识别的。Flags对于 command packet 是固定值 0。Command Set相当于一个command的分组,一些功能相近的command被分在同一个Command Set中。

图三command packet包头结构

reply packet包头结构如图四所示,Length和Id作用与command packet中的一样。Flags对于reply packet值是固定值 0x80。Error Code用来表示被回复的命令是否被正确执行了。0表示正确,非0表示执行错误。

图四reply packet包头结构

Debugger和Target JVM都可以发送命令包。Debugger通过发送命令包获取Target JVM的信息以及控制程序的执行。Target JVM通过发送命令包通知Debugger诸如断点或者异常等事件的发生。

 

JDWP传输接口

JDWP本身是与传输层独立的,但JDWP提供了一套传输接口,它定义了一系列的方法用来定义JDWP与传输层之间的交互方式。传输层必须以动态链接库的方式实现,并且暴露一系列的标准接口供JDWP使用。

当JDWP agent被Java虚拟机加载后,JDWP会根据参数去加载指定的传输层实现(例如我们最常见的Sun的JDK提供了socket方式)。传输层的实现必须暴露jdwpTransport_OnLoad接口,JDWP agent在加载传输层动态链接库后会调用该接口进行传输层的初始化。接口定义如下:

和一般的连接方式类似,JDWP可以主动去连接debugger,也可以等待debugger的连接。对于主动去连接debugger,需要调用Attach方法,其定义如下:

对于JDWP等待debugger连接的方式,首先要调用其StartListening方法,定义如下:

该方法将使JDWP处于监听状态,随后调用Accept方法接收连接:

根据传入参数确定采用主动连接方式还是等待连接方式,无论是哪种连接方式,在连接建立后,会立即进行握手操作。握手完成后可以通过IO操作进行应答。

 

JDWP命令机制

JDWP内部命令实现机制如图五所示,JDWP接收和发送的包都会在 TransportManager进行处理。TransportManager的主要作用就是负责解析接收到的 JDWP的command packet以及将reply packet在发送前进行打包。对于接收到的command packet,TransportManager处理后转给PacketDispatcher,进一步封装后会继续转到CommandDispatcher。CommandDispatcher会根据命令中提供的命令组号和命令号创建一个CommandHandler来处理JDWP命令。CommandHandler才是真正执行JDWP命令的类,每个JDWP命令都有一个相对应的CommandHandler的子类,当接收到某个命令时,就会创建对应子类的实例,调用对应的JVMTI方法进行处理。

图五JDWP命令机制

 

Java调试接口(JDI)

JDI主要包含了一套针对前端定义的接口,通过这套接口,开发人员就能通过前端虚拟机上的调试器(如eclipse调试器)来远程监控/控制后端虚拟机上被调试程序的运行,它处于JPDA体系的最高层,eclipse的jdt.debug就是一个完整的JDI实现。

 

JDI工作方式

首先,Debugger通过Bootstrap获取唯一的虚拟机管理器。

链接是Debugger与Target JVM之间交互的渠道,一个调试器可以链接多个目标虚拟机,但一个目标虚拟机最多只能链接一个调试器。链接是由链接器(Connector)生成的,不同的链接器有着不同的实现方式。JDI中定义了三种链接器接口,分别是依附型链接器(AttachingConnector)、监听型链接器(ListeningConnector)和启动型链接器(LaunchingConnector)。在调试过程中,实际使用的链接器必须实现其中一种接口,而在虚拟机管理器中就提供了各种连接器的实现。

根据调试器在链接过程中扮演的角色,也可以将链接方式划分为主动链接和被动链接。主动链接表示调试器主动地向目标虚拟机发起链接。被动链接表示调试器将被动地等待或者监听由目标虚拟机发起的链接。以我们调试一个Test类的main方法为例,这是一个典型的被动链接。

首先调试器调用虚拟管理器的listeningConnectors ()方法获取所有的监听型链接器实例connector;根据socket传输方式选择对应的socket链接器,调用链接器的startListening() 方法让链接器进入监听状态;终端用户以-agentlib:jdwp=transport=dt_socket,suspend=y,address=localhost:port参数启动target JVM(即启动时加载jdwp Agent,transport=dt_socket 表示采用socket连接方式, suspend=y参数表示虚拟机启动后挂起,等待连接建立后再执行,address是socket连接地址),然后调用链接器的accept()方法等待接受目标虚拟机的链接,链接成功后进行握手,确保通信正常,该方法返回目标虚拟机的实例。

自此Debugger和Target JVM便可以进行双向通信了。通过VirtualMachine负责发送命令和接收回应,Debugger将用户的操作按JDWP协议转化为调试命令发送到前端Target JVM上,经由JDWP的调试机制,将调试的结果按JDWP协议发回给Debugger;最后,Debugger解析后将可视化数据信息反馈给用户。

对于其他的链接方式,虚拟机管理器都提供了相对应的链接器,按照指定的链接方式完成链接后均可获得目标虚拟机实例。

 

JDI数据模块

上面介绍到的VirtualMachine接口如图六所示,该接口提供了方法可以用来直接或间接地获取target JVM上所有的数据和状态信息,也可以挂起、恢复、终止目标虚拟机。

图六 VirtualMachine接口

 

VirtualMachine接口继承自Mirror接口,JDI中几乎所有其他接口都继承于它。镜像机制是将目标虚拟机上的所有数据、类型、域、方法、事件、状态和资源,以及调试器发向目标虚拟机的事件请求等都映射成Mirror对象。例如,在目标虚拟机上,对象实例被映射成ObjectReference镜像,基本类型的值(如float等)被映射成PrimitiveValue(如FloatValue等)。被调试的目标程序的运行状态信息被映射到StackFrame镜像中,被调试的目标虚拟机则被映射成VirtualMachine镜像等。调用Mirror实例virtualMachine()可以获取其虚拟机信息

这样,通过virtualMachine实例可以方便的获取JVM当前状态或者控制JVM。

 

结束语

本文较详细的介绍了JPDA的整个体系,分别介绍了其三个模块之间的层次关系,让大家对调试器的整个工作原理,从调试器到目标虚拟机如何进行通信有了一个直观的了解。在本文基础上,大家可以进一步理解JPDA相关细节,最终能够自己编写出实用、高效的Java调试器程序。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Java调试原理初探 的相关文章

  • SPNEGO 密码身份验证问题

    我已将我的应用程序配置为通过 SPNEGO 与 Websphere 使用 Kerberos 身份验证 这是详细信息 krb5 conf libdefaults default realm ABC MYCOMPANY COM default
  • 从文本文件中读取阿拉伯字符

    我完成了一个项目 在该项目中我读取了用记事本编写的文本文件 我的文本文件中的字符是阿拉伯语 文件编码类型是UTF 8 当在 Netbeans 7 0 1 中启动我的项目时 一切似乎都正常 但是当我将项目构建为 jar 文件时 字符以这种方式
  • 将构造函数作为参数传递给方法

    我是java新手 开始研究构造函数 我看到一些构造函数作为参数传递给方法的示例 请告诉我当构造函数作为参数传递给方法时会发生什么 或者建议我一些链接 我可以在其中获得有关使用构造函数的足够知识 根据您需要传递构造函数的目的 您可以考虑传递供
  • 使类只能从特定类实例化

    假设我有 3 节课class1 class2 and class3 我怎样才能拥有它class1只能通过实例化class2 class1 object new class1 但不是 class3 或任何其他类 我认为它应该与修饰符一起使用
  • 重写 getPreferredSize() 会破坏 LSP

    我总是在这个压倒一切的网站上看到建议getPreferredSize 而不是使用setPreferredSize 例如 如前面的线程所示 对于固定大小的组件 使用重写 getPreferredSize 而不是使用 setPreferredS
  • 记录共享和映射的诊断上下文

    据我所知 其他人做了什么来解决 Commons Logging 项目 针对 NET 和 Java 不支持映射或嵌套诊断上下文这一事实 执行摘要 我们选择直接使用实现者日志框架 在我们的例子中为 log4j 长答案 您是否需要一个抽象日志框架
  • JavaFX使节点覆盖父节点边框颜色

    我有一个如下所示的节点 仅使用 css 我希望标签覆盖其父边框颜色 因此标签下方的边框颜色部分变得不可见 我用来制作这个边框的CSS代码 fx border color black fx border width 3 fx border r
  • 确定序列化对象的类型

    我需要通过套接字发送消息 从用户到引擎的请求 以及从引擎到用户的响应 所以流程本质上是 serialized request Server lt network gt Client serialized response request r
  • java setFullScreenWindow 在 Mac 中隐藏登录对话框

    我使用的是全屏窗口 类似于屏幕保护程序 使用这里的方法 GraphicsEnvironment getLocalGraphicsEnvironment getDefaultScreenDevice setFullScreenWindow t
  • 用于层次结构树角色的 Spring Security / Java EE 解决方案

    我知道 Spring Security 非常适合标准角色和基于权限的授权 我不确定的是这种情况 系统中管理着 10 000 名员工 员工被组织成组织结构图 跨部门的谁向谁报告的树 其中一些员工是用户 这些用户仅被允许访问其职责范围内的员工
  • 拆分/标记化/扫描字符串并注意引号

    Java中是否有默认 简单的方法来分割字符串 但要注意引号或其他符号 例如 给定以下文本 There s a man that live next door in my neighborhood and he gets me down Ob
  • 嵌套字段的 Comparator.comparing(...)

    假设我有一个这样的域模型 class Lecture Course course getters class Course Teacher teacher int studentSize getters class Teacher int
  • 当底层连接是有状态时如何使用 Apache HttpClient?

    我在谷歌上搜索了很多关于如何使用 HttpClient 进行多线程处理的信息 他们中的大多数人建议使用 ThreadSafeClientConnManager 但我的应用程序必须登录某个主机 登录表单页面 以便 HttpClient 获得底
  • javax.media.jai 类的公共下载?

    这是一个非常简单的问题 我一直在寻找可以下载 javax media jai 库的地方 我找到了 jai imageio 库 但是我发现的所有其他 jai 内容要么已经过时 2008 年及之前 然后我遇到了登录屏幕 是否有 javax me
  • java中使用多线程调用同一类的不同方法

    我有一个类 如下所示 具有三种方法 public class MyRunnable implements Runnable Override public void run what code need to write here to c
  • 让 Hibernate 和 SQL Server 与 VARCHAR 和 NVARCHAR 良好配合

    我目前正在大型数据库的某些表中启用 UTF 8 字符 这些表已经是 MS SQL 类型 NVARCHAR 此外 我还有几个使用 VARCHAR 的字段 Hibernate 与 JDBC 驱动程序的交互存在一个众所周知的问题 例如 参见在 h
  • Axis2 错误:要输出的文本中的空白字符 (0x4) 无效

    我创建了一个 Java 客户端 使用 Axis2 1 7 6 作为代码生成器与 SOAP Web 服务进行交互 问题在于客户端的某些输入抛出异常并显示以下消息 org apache axis2 AxisFault Invalid white
  • 如何建立与 FileZilla Server 1.2.0 的 FTPS 数据连接

    使用 Apache commons net 的 Java FTPSClient 进行会话恢复是一个已知问题 会话恢复是 FTPS 服务器数据连接所需的一项安全功能 Apache FTPSClient 不支持会话恢复 并且 JDK API 使
  • Errors/BindingResult 参数应在模型属性、@RequestBody 或 @RequestPart 参数之后立即声明

    我通过剖析示例应用程序来自学 Spring 然后到处添加代码来测试我在剖析过程中开发的理论 在测试添加到 Spring 应用程序中的一些代码时 我收到以下错误消息 An Errors BindingResult argument is ex
  • Java中单例的其他方式[重复]

    这个问题在这里已经有答案了 只是我在考虑编写单例类的其他方法 那么这个类是否被认为是单例类呢 public class MyClass static Myclass myclass static myclass new MyClass pr

随机推荐

  • PHP SQL实现公司数据库的增删改查

    文末附文件 题目要求 Use the following SQL DDL statements to create the six tables required for this project Note that you need to
  • python之celery

    Celery是由Python开发的一个简单 灵活 可靠的处理大量任务的分发系统 可以实时处理任务 也可以定时异步处理任务 每次分发任务后得到一个ID 然后根据这个ID查询任务执行情况 安装 pip install celery eventl
  • sqllabs详解与知识点汇总(内含代码审计)

    sqllabs 1 65 详解 关于注释符的详解 SQL注入注释符 使用条件及其他注释方式的探索 impulse 博客园 cnblogs com HTTP请求方法 GET 对比 POST HTTP 方法 GET 对比 POST 菜鸟教程 r
  • docker基本操作

    Docker官方建议在Ubuntu中安装 建议安装在CentOS7 X以上版本 1 安装Docker 1 yum包更新到最新 sudo yum update 2 安装需要的软件包 yum util提供yum config manager功能
  • java.math.BigDecimal用法

    Java在java math包中提供的API类BigDecimal 用来对超过16位有效位的数进行精确的运算 双精度浮点型变量double可以处理16位有效数 在实际应用中 需要对更大或者更小的数进行运算和处理 float和double只能
  • 继承和多态的内存图解

    今天被继承和多态困扰 在CSDN上找了好几个内存分配讲解 个人感觉不全吧 就把他们做了个整合 讲解的是多态的方法和成员调用和继承中的方法和变量的调用 什么是多态 同一个对象 在不同时刻表现出来的不同形态 多态的前提 要有继承或实现关系要有方
  • web robotframework xpath元素定位

    1 定位购买按钮 在这里 我写的是 td class text center button class ng isolate scope span text 购买 提示找不到元素 原因是button的class值 我把他改成class bt
  • 调试osgEarth(七)地图map图层的构建过程-添加layer(4)--打开ImageLayer

    继续调试 创建空影像 建了个1x1x1的空图片 这个也比较简单 ImageLayer建立了一个1x1x1的空图片
  • spring boot 2.x 应用可视化监控

    来源 简书 内容 应用可视化监控 prometheus grafana https www jianshu com p 7ecb57a3f326 修改为spring boot 2 0时 1 首先 添加依赖如下依赖
  • E: Unable to locate package kubelet 解决

    昨天搭建k8s集群环境时 安装报错 显示无法找到 1 打开vim etc apt sources list 写入阿里云的源 deb https mirrors aliyun com kubernetes apt kubernetes xen
  • aiVMS----CentOS7.6安装RabbitMQ安装

    entOS7 6安装RabbitMQ安装 安装一 快速的安装方法是使用Package Cloud提供的脚本 Package Cloud也可以用于通过yum安装最新的Erlang版本 使用PackageCloud安装RabbitMQ 官网参考
  • table问题总结

    前景 最近开发需要原生table 之前使用很少用 了解比较少 这次对于样式和功能要求也比较高 对与遇到的问题做下总结和分享 问题与解决方案 行高不定问题 描述 表格每一行的高度不确定 会自动适配 设置行高和高度均无效 产生原因 表格设置了固
  • R语言用ROCR包出现载入程辑包:‘gplots’ The following object is masked from ‘package:stats’错误

    谢谢点进来 如果你觉得有帮助 麻烦点个赞 假如在R studio运行的代码是这样的 library ROCR 首先看到这个问题的时候 我认为没有安装gplots包 可以按下图所示看是否有该包 如果没有则点击install输入包名安装 奇怪的
  • Ledger of Harms

    Under immense pressure to prioritize engagement and growth technology platforms have created a race for human attention
  • JavaScript快速排序算法

  • C#单线程和多线程端口扫描器

    C 单线程和多线程端口扫描器 一 项目创建以及页面设计 一 项目新建 二 页面设计 二 单线程实现端口扫描 一 代码实现 二 运行结果 三 多线程实现端口扫描 一 程序实现 二 运行结果 四 总结 五 参考资料 一 项目创建以及页面设计 一
  • JCenter下载太慢?教你修改Maven仓库地址为国内镜像

    转载自 http www yrom net blog 2015 02 07 change gradle maven repo url 近来迁移了一些项目到Android Studio 采用Gradle构建确实比原来的Ant方便许多 但是编译
  • StyleCLIP学习笔记

    https github com orpatashnik StyleCLIP The main inferece script is placed in mapper scripts inference py Inference argum
  • 安装librocksdb.so.4.1的共享库

    安装librocksdb so 4 1的共享库 注 以下命令需在root模式下进行 1 clone rocksDB 命令行运行git clone https github com facebook rocksdb git 2 切换到4 1
  • Java调试原理初探

    对于所有程序员 程序调试是一项必备的技能 在java程序中 最简单的就是通过 System out println 来打印输出各种变量来发现问题 而用的最多的莫过于通过各种调试器来进行调试 如图一所示的eclipse调试器 甚至还可以进行远