0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

反向 Debug 了解一下?揭秘 Java DEBUG 的基本原理

京东云 来源:jf_75140285 作者:jf_75140285 2024-11-25 11:15 次阅读

Debug 的时候,都遇到过手速太快,直接跳过了自己想调试的方法、代码的时候吧……

一旦跳过,可能就得重新执行一遍,准备数据、重新启动可能几分钟就过去了。

好在IDE 们都很强大,还给你后悔的机会,可以直接删除某个 Stack Frame,直接返回到之前的状态,确切的说是返回到之前的某个 Stack Frame,从而实现让程序“逆向运行”。

wKgaomdD69uABq9tAAb9sMNBUsc682.png

这个 Reset Frame 的能力,可不只是返回上一步,上 N 步也是可以的;选中你期望的那个帧,直接Reset Frame/Drop Frame,可以直接回到调用栈上的某个栈帧,时间反转!

可惜这玩意也不是那么万能,毕竟是通过 stack pop 这种操作实现,实际上只是给调用栈栈顶的 N 个 frame pop 出来而已,还谈不上是真正的“反向 DEBUG”。

相比之下, GDB 的 Reverse Debugging 就比较强大,真正的 “反向” DEBUG,逆向运行,实现回放。

所以吧在运行过程中,已经修改的数据,比如引用传递的方法参数、变量,一旦修改肯定回退不了,不然真的成时光机了。

这些乱七八糟的调试功能,都是基于 Java 内置的 Debug 体系来实现的。

JAVA DEBUG 体系

Java 提供了一个完整的 Debug 体系 JPDA (Java Platform Debugger Architecture),这个 JPDA 架构体系由 3 部分组成:

JVM TI - Java VM Tool Interface

JDWP - Java Debug Wire Protocol

JDI - Java Debug Interface

如果结合IDE 来看,那么一个完整的 Debug 功能看起来就是这个样子:

wKgZomdD69yAHuo6AALh88KXiIA805.png

解释一下这个体系:

JVM TI 是一个 JVM 提供的一个调试接口,提供了一系列控制 JVM 行为的功能,比如分析、调试、监控、线程分析等等。也就是说,这个接口定义了一系列调试分析功能,而 JVM 实现了这个接口,从而提供调试能力。

不过吧,这个接口毕竟是 C++的,调用起来确实不方便,所以Java 还提供了 JDI 这么个 Java 接口。

JDI 接口使用 JDWP 这个私有的应用层协议,通过 TCP 和目标 VM 的 JVMTI 接口进行交互。

也可以把简单这个 JDWP 协议理解为 JSF/Dubbo 协议;相当于 IDE 里通过 JDI 这个 SDK,使用 JDWP 协议调用远程 JVMTI 的 RPC 接口,来传输调试时的各种断点、查看操作。

可能有人会问,搞什么套壳!要什么 JDWP,我直接 JVMTI 调试不是更香,链路越短性能越高!

当然可以,比如 Arthas 里的部分功能,就直接使用了 JVMTI 接口,要什么 JDI!直接 JVMTI 干就完了。

开个玩笑,Arthas 毕竟不是 Debug 工具,人家根本就不用 JDI 接口。而且 JVMTI 的能力也不只是断点,它的功能非常多:

wKgaomdD692AK-MWAAbr688q4t0961.png

左边的功能类,提供了各种乱七八糟的功能,比如我们常用的添加一个断点:

jvmtiError
SetBreakpoint(jvmtiEnv* env,
            jmethodID method,
            jlocation location)

右边的事件类,可以简单的理解为回调;还是拿断点举例,如果我用上面的 SetBreakpoint 添加了一个断点,那么当执行到该位置时,就会触发这个事件:

void JNICALL
Breakpoint(jvmtiEnv *jvmti_env,
            JNIEnv* jni_env,
            jthread thread,
            jmethodID method,
            jlocation location)

JVMTI 的功能非常之多,而 JDI 只是实现了部分 JVMTI 的方法,所以某些专业的 Profiler 工具,可能会直接使用 JVMTI,从而实现更丰富的诊断分析功能。

远程调试与本地调试

不知道大家有没有留意过本地 Debug 启动时的日志:

wKgZomdD69-ACxPvAADllqU4lss011.png

第一行是隐藏了后半段的启动命令,展开后是这个样子:

/path/to/java -agentlib:jdwp=transport=dt_socket,address=127.0.0.1:53631,suspend=y,server=n -javaagent:/path/to/jetbrains/debugger-agent.jar ...

第二行是一个 Connected 日志,意思是使用 socket 连接到远程 VM 的53631端口

上一段说到,IDE 通过 JDI 接口,使用 JDWP 协议和目标 VM 的 JVMTI 交互。这里的 53631 端口,就是目标 JVM 暴露出的 JVM TI 的 server 端口。

而第一行里,IDEA 自动给我们加上了 -agentlib:jdwp=transport=dt_socket,address=127.0.0.1:53631 这么一段,这个参数的意思就是,让 jvm 以 53631 暴露 jdwp 协议

小知识,这个 agentlib 可不只是为 jvmti 提供的。它还可以让 JVM 加载其他的 native lib包,直接“外挂”到你的 jvm 上,下面是“外挂”的参数格式:

wKgaomdD6-CAOYW2AAEqnSKR9yo953.png

所以吧,上面的描述其实不太严谨,更专业的说法是:

让 JVM 加载 JDWP 这个 agent 库,参数为transport=dt_socket,address=127.0.0.1:53631 ,这个 jdwp agent 库以 53631 端口提供了 jdwp 协议的 server。只不过这个 jdwp 是jvm 内部的库,不需要额外的 so/dylib/dll 文件。

如有需要,你完全可以弄个 “datupiao” 的 agentlib,“外挂”到这个 jvm 上,然后在这个 lib 里调用 JVMTI 接口,然后暴露个端口提供服务和远程交互,实现自己的 jdwp!

可能某些老板们注意到了,本地调试还要127.0.0.1走tcp 交互一遍,那远程调试呢?

基于上面的解释,本地调试和远程调试真的没啥区别!或者说,在目前 IDEA/Eclipse 的实现下,不存在本地调试,都是远程!只不过一个是 127.0.0.1,一个是远程的 IP 而已。

在本地调试时,IDEA 会自动给我们的 JVM 增加 agent 参数,随机指定一个端口,然后通过 JDI 接口连接,代码大概长这样(JDI 的 SDK 在 JDK_HOME/lib/tools.jar ):

Map< String, Connector.Argument > env = connector.defaultArguments();
env.get("hostname").setValue(hostname);
env.get("port").setValue(port);

VirtualMachine vm = connector.attach(env);

瞅瞅, VirtualMachine 里的就这点方法,能力上比 JVMTI 还是差远了

List< ReferenceType > classesByName(String className);

List< ReferenceType > allClasses();

void redefineClasses(Map< ? extends ReferenceType, byte[] > classToBytes);

List< ThreadReference > allThreads();

void suspend();

void resume();

List< ThreadGroupReference > topLevelThreadGroups();

EventQueue eventQueue();

EventRequestManager eventRequestManager();

VoidValue mirrorOfVoid();

Process process();

再回来看看 IDEA 中独立的远程调试,配置好之后,红框里的信息会提示你 ,远程的 JVM 需增加这一段启动参数,而且支持多个版本 JDK 的格式,CV 大法就能直接用。

wKgZomdD6-CAFWzPAAHEdznUmT4345.png

-agentlib 和 -javaagent

有些细心的同学可能发现了,IDEA 默认的启动脚本里,同时配置了 -agentlib 和 -javaagent。

-javaagent:/path/to/jetbrains/debugger-agent.jar 

这个 debugger-agent吧,其实也没干啥事,只是对 JDK 内置的一些线程做了些增强,辅助 IDEA 的 debug 功能,支持一些异步的调试。

wKgaomdD6-KAfwr8AAyvMfA8bTM822.png

agentlib、javaagent 这俩兄弟,定位其实很像,都是加载自定义的代码。

不过区别在于,agentlib 是加载 native lib,需要c/cpp 去写,相当于外挂自己的代码在 jvm 上,可以为所欲为,比如在 agentlib 里调用上面说的 JVMTI 。

而 javaagent 是用 java 写的,可以直接用上层的 Instrumentation API,做一些类的增强转换之类,这也是大多数 APM Agent、Profiler Agent实现的基本原理。

Arthas 的玩法

Arthas 的核心入口,其实还是 javaagent,支持静态加载和动态加载两种玩法。

静态没啥好说的,启动脚本里增加一个-javaagent:/tmp/test/arthas-agent.jar,然后为所欲为。

wKgZomdD6-SAOD_NAAE7RdOEjg4491.png

动态的叫 attach,使用 Java 提供的 VirtualMachine 就可以实现运行时添加 -javaagent,效果一样:

VirtualMachine virtualMachine = VirtualMachine.attach(virtualMachineDescriptor);
virtualMachine.loadAgent(agentPath, agentArgs);

这个 Agent 在 JVM 里启动了一个TCP server,用于收发 Arthas Client 的各种 trace、watch 、Dashboard 等指令,然后通过 Instrumentation 增强Class 插入代码、或者直接调用某些 Java API,实现各种功能。

注意到了吗?Arthas 可以直接下载一个 jar 包,java -jar 就能连上。

其实吧,它这个直接启动的 jar 包,是一个 boot 包,启动之后把乱七八糟的 jar 都下载下来。接着动态 attach 的方式,连接到本机指定进程号的 JVM,然后再为所欲为。

在 3.5 版本之后,Arthas 还新增了一个 vmtool 命令,这个命令可以直接获取内存中的指定对象实例。

$ vmtool --action getInstances --className java.lang.String --limit 10
@String[][
    @String[com/taobao/arthas/core/shell/session/Session],
    @String[com.taobao.arthas.core.shell.session.Session],
    @String[com/taobao/arthas/core/shell/session/Session],
    @String[com/taobao/arthas/core/shell/session/Session],
    @String[com/taobao/arthas/core/shell/session/Session.class],
    @String[com/taobao/arthas/core/shell/session/Session.class],
    @String[com/taobao/arthas/core/shell/session/Session.class],
    @String[com/],
    @String[java/util/concurrent/ConcurrentHashMap$ValueIterator],
    @String[java/util/concurrent/locks/LockSupport],
]

直接获取内存对象,这玩意只靠 Instrumentation API 可做不到。Arthas 搞了个骚操作,直接 JNI 调用自定义 lib,用过 cpp 直接调用了 JVMTI 的 API,融合了 Instrumentation 和 JVMTI 的能力,这下是真的为所欲为了!

#include < stdio.h >
#include < jni.h >
#include < jni_md.h >
#include < jvmti.h >
#include "arthas_VmTool.h" // under target/native/javah/

static jvmtiEnv *jvmti;

...

extern "C"
JNIEXPORT jobjectArray JNICALL
Java_arthas_VmTool_getInstances0(JNIEnv *env, jclass thisClass, jclass klass, jint limit) {
    jlong tag = getTag();
    limitCounter.init(limit);
    jvmtiError error = jvmti->IterateOverInstancesOfClass(klass, JVMTI_HEAP_OBJECT_EITHER,
                                               HeapObjectCallback, &tag);
    if (error) {
        printf("ERROR: JVMTI IterateOverInstancesOfClass failed!%un", error);
        return NULL;
    }

    jint count = 0;
    jobject *instances;
    error = jvmti->GetObjectsWithTags(1, &tag, &count, &instances, NULL);
    if (error) {
        printf("ERROR: JVMTI GetObjectsWithTags failed!%un", error);
        return NULL;
    }

    jobjectArray array = env->NewObjectArray(count, klass, NULL);
    //添加元素到数组
    for (int i = 0; i < count; i++) {
        env- >SetObjectArrayElement(array, i, instances[i]);
    }
    jvmti->Deallocate(reinterpret_cast< unsigned char * >(instances));
    return array;
}

总结

Debug 基于 JDPA 体系

IDE 直接接入 JDPA 体系中的 JDI 接口完成

JDI 通过 JDWP 协议,调用远程 VM 的 JVMTI 接口

JDWP 是通过 agentlib 加载的,agentlib 算是一个 native 的静态“外挂”接口

javaagent 是 JAVA 层面的“外挂”接口,用过 Instrumentation API(Java)实现各种功能,主要用于APM、Profiler 工具

如果你想,在 javaagent 里调用功能更丰富的 JVMTI 也不是不行。

审核编辑 黄宇

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • JAVA
    +关注

    关注

    19

    文章

    2967

    浏览量

    104739
  • IDE
    IDE
    +关注

    关注

    0

    文章

    338

    浏览量

    46751
  • DEBUG
    +关注

    关注

    3

    文章

    93

    浏览量

    19915
收藏 人收藏

    评论

    相关推荐

    详解Java DEBUG基本原理

    Debug 的时候,都遇到过手速太快,直接跳过了自己想调试的方法、代码的时候吧……
    的头像 发表于 01-05 10:10 1374次阅读
    详解<b class='flag-5'>Java</b> <b class='flag-5'>DEBUG</b>的<b class='flag-5'>基本原理</b>

    先来认识一下正激的基本原理

    我们先来认识一下正激的基本原理,如图所示:正激在原边加正向电压 MOS 管导通时,副边的输出符合变压器原理,即:Vs = n* VinVs :变压器副边输出电压n:匝比 = Ns/NpVin:输入电压由上式可知正激拓扑不难理解。* 注意:原边还有个绕组称为复位绕组,副边有
    发表于 12-31 06:18

    DEBUG程序的使用

    实 验   DEBUG程序的使用 、 实验目的 1. 学习使用DEBUG程序的各种命令
    发表于 09-28 23:10 7341次阅读

    Vivado+FPGA:如何使用Debug Cores(ILA)在线调试

    在Vivado下在线调试是利用ILA进行的,Xilinx官方给出了个视频,演示了如何使用Vivado的debug cores,下面我根据这个官方视频的截图的来演示一下: 官方的视频使用的软件版本为
    发表于 02-08 08:52 2636次阅读

    Vivado中使用debug工具步骤与调试技巧

    在ISE中称为ChipScope而Vivado中就称为in system debug。下面就介绍Vivado中如何使用debug工具。 Debug分为3个阶段: 1. 探测信号:在设计中标志想要查看的信号 2. 布局布线:给包含
    发表于 11-17 14:05 6w次阅读
    Vivado中使用<b class='flag-5'>debug</b>工具步骤与调试技巧

    STM32调试DEBUG时需要了解那些知识相关资料概述

    学习STM32开发,肯定少不了DEBUG调试这步骤。那么,本文带你了解一下这个调试相关的知识。本文以STM
    的头像 发表于 11-11 11:39 7061次阅读
    STM32调试<b class='flag-5'>DEBUG</b>时需要<b class='flag-5'>了解</b>那些知识相关资料概述

    关于Vivado中三种操作Debug的方式

    Vivado中提供了多种Debug的操作方式,下面就来总结一下: 1. 代码中例化ILA IP核 第种,直接例化ILA IP核: 需要探测多少个信号,信号的位宽是多少,直接选择即可: 下面界面可以
    的头像 发表于 11-11 17:07 1.1w次阅读
    关于Vivado中三种操作<b class='flag-5'>Debug</b>的方式

    含泪Debug:STM32与ESP32 SPI通信避坑

    最近搞了一下ESP32与STM32之间的SPI通信,这路曲曲折折耗费了很长时间,下面我把我的debug踩坑经历奉献出了,希望大家不要fanwo
    发表于 12-24 19:07 36次下载
    含泪<b class='flag-5'>Debug</b>:STM32与ESP32 SPI通信避坑

    STM32 Debug无法添加断点的解决方法

    目录STM32 Debug无法添加断点的解决方法1、run to main设置错误2、generate assembler SRC file 设置错误3、HEX文件命名错误4、优化等级选择错误
    发表于 01-12 18:48 8次下载
    STM32 <b class='flag-5'>Debug</b>无法添加断点的解决方法

    STM32(Cortex-M)内核DEBUG调试接口知识

    学习STM32开发,肯定少不了debug调试这步骤。那么,本文带你了解一下这个调试相关的知识。
    发表于 02-08 16:02 9次下载
    STM32(Cortex-M)内核<b class='flag-5'>DEBUG</b>调试接口知识

    debug 吞吐量的办法

    Debug 网络质量的时候,我们般会关注两个因素:延迟和吞吐量(带宽)。延迟比较好验证,Ping 一下或者 mtr[1] 一下就能看出来。这篇文章分享
    的头像 发表于 08-23 09:17 966次阅读

    debug 吞吐量的办法

    Debug 网络质量的时候,我们般会关注两个因素:延迟和吞吐量(带宽)。延迟比较好验证,Ping 一下或者 mtr[1] 一下就能看出来。这篇文章分享
    的头像 发表于 09-02 09:36 869次阅读

    IAR中 Debug 和 Release有何区别

    ;   Release, 即发布版本,或者说最终释放版本。   在些项目中,会出现Debug 和 Release两个版本,比如:   IAR EWARM:   VS:   些初学者可能会问,
    的头像 发表于 05-22 10:54 2838次阅读
    IAR中 <b class='flag-5'>Debug</b> 和 Release有何区别

    一下电荷泵升压的基本原理

    电荷泵基于个物理学的基本原理:在闭合电路中来回流动的电荷不会消失。
    的头像 发表于 08-15 15:38 3227次阅读
    讲<b class='flag-5'>一下</b>电荷泵升压的<b class='flag-5'>基本原理</b>

    OpenCV4.8在Debug模式滚动条错误问题解决与原因查找

    不同的是他在Debug模式执行的而我在Release模式执行的。
    的头像 发表于 10-23 10:19 514次阅读
    OpenCV4.8在<b class='flag-5'>Debug</b>模式<b class='flag-5'>下</b>滚动条错误问题解决与原因查找