当前位置:首页 > 科技  > 软件

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

来源: 责编: 时间:2024-01-03 11:37:17 349观看
导读Debug 的时候,都遇到过手速太快,直接跳过了自己想调试的方法、代码的时候吧……一旦跳过,可能就得重新执行一遍,准备数据、重新启动可能几分钟就过去了。好在IDE 们都很强大,还给你后悔的机会,可以直接删除某个 Stack Frame

Debug 的时候,都遇到过手速太快,直接跳过了自己想调试的方法、代码的时候吧……FE528资讯网——每日最新资讯28at.com

一旦跳过,可能就得重新执行一遍,准备数据、重新启动可能几分钟就过去了。FE528资讯网——每日最新资讯28at.com

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

FE528资讯网——每日最新资讯28at.com

这个 Reset Frame 的能力,可不只是返回上一步,上 N 步也是可以的;选中你期望的那个帧,直接Reset Frame/Drop Frame,可以直接回到调用栈上的某个栈帧,时间反转!FE528资讯网——每日最新资讯28at.com

可惜这玩意也不是那么万能,毕竟是通过 stack pop 这种操作实现,实际上只是给调用栈栈顶的 N 个 frame pop 出来而已,还谈不上是真正的“反向 DEBUG”。FE528资讯网——每日最新资讯28at.com

相比之下, GDB 的 Reverse Debugging 就比较强大,真正的 “反向” DEBUG,逆向运行,实现回放。FE528资讯网——每日最新资讯28at.com

所以吧在运行过程中,已经修改的数据,比如引用传递的方法参数、变量,一旦修改肯定回退不了,不然真的成时光机了。FE528资讯网——每日最新资讯28at.com

这些乱七八糟的调试功能,都是基于 Java 内置的 Debug 体系来实现的。FE528资讯网——每日最新资讯28at.com

JAVA DEBUG 体系

Java 提供了一个完整的 Debug 体系 JPDA (Java Platform Debugger Architecture),这个 JPDA 架构体系由 3 部分组成:FE528资讯网——每日最新资讯28at.com

  1. JVM TI - Java VM Tool Interface
  2. JDWP - Java Debug Wire Protocol
  3. JDI - Java Debug Interface

如果结合IDE 来看,那么一个完整的 Debug 功能看起来就是这个样子:FE528资讯网——每日最新资讯28at.com

FE528资讯网——每日最新资讯28at.com

解释一下这个体系:FE528资讯网——每日最新资讯28at.com

JVM TI 是一个 JVM 提供的一个调试接口,提供了一系列控制 JVM 行为的功能,比如分析、调试、监控、线程分析等等。也就是说,这个接口定义了一系列调试分析功能,而 JVM 实现了这个接口,从而提供调试能力。FE528资讯网——每日最新资讯28at.com

不过吧,这个接口毕竟是 C++的,调用起来确实不方便,所以Java 还提供了 JDI 这么个 Java 接口。FE528资讯网——每日最新资讯28at.com

JDI 接口使用 JDWP 这个私有的应用层协议,通过 TCP 和目标 VM 的 JVMTI 接口进行交互。FE528资讯网——每日最新资讯28at.com

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

可能有人会问,搞什么套壳!要什么 JDWP,我直接 JVMTI 调试不是更香,链路越短性能越高!FE528资讯网——每日最新资讯28at.com

当然可以,比如 Arthas 里的部分功能,就直接使用了 JVMTI 接口,要什么 JDI!直接 JVMTI 干就完了。FE528资讯网——每日最新资讯28at.com

开个玩笑,Arthas 毕竟不是 Debug 工具,人家根本就不用 JDI 接口。而且 JVMTI 的能力也不只是断点,它的功能非常多:FE528资讯网——每日最新资讯28at.com

FE528资讯网——每日最新资讯28at.com

左边的功能类,提供了各种乱七八糟的功能,比如我们常用的添加一个断点:FE528资讯网——每日最新资讯28at.com

jvmtiErrorSetBreakpoint(jvmtiEnv* env,            jmethodID method,            jlocation location)

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

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

JVMTI 的功能非常之多,而 JDI 只是实现了部分 JVMTI 的方法,所以某些专业的 Profiler 工具,可能会直接使用 JVMTI,从而实现更丰富的诊断分析功能。FE528资讯网——每日最新资讯28at.com

远程调试与本地调试

不知道大家有没有留意过本地 Debug 启动时的日志:FE528资讯网——每日最新资讯28at.com

FE528资讯网——每日最新资讯28at.com

第一行是隐藏了后半段的启动命令,展开后是这个样子:FE528资讯网——每日最新资讯28at.com

/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端口FE528资讯网——每日最新资讯28at.com

上一段说到,IDE 通过 JDI 接口,使用 JDWP 协议和目标 VM 的 JVMTI 交互。这里的 53631 端口,就是目标 JVM 暴露出的 JVM TI 的 server 端口。FE528资讯网——每日最新资讯28at.com

而第一行里,IDEA 自动给我们加上了 -agentlib:jdwp=transport=dt_socket,address=127.0.0.1:53631 这么一段,这个参数的意思就是,让 jvm 以 53631 暴露 jdwp 协议FE528资讯网——每日最新资讯28at.com

小知识,这个 agentlib 可不只是为 jvmti 提供的。它还可以让 JVM 加载其他的 native lib包,直接“”到你的 jvm 上,下面是参数格式:FE528资讯网——每日最新资讯28at.com

FE528资讯网——每日最新资讯28at.com

所以吧,上面的描述其实不太严谨,更专业的说法是:FE528资讯网——每日最新资讯28at.com

让 JVM 加载 JDWP 这个 agent 库,参数为transport=dt_socket,address=127.0.0.1:53631 ,这个 jdwp agent 库以 53631 端口提供了 jdwp 协议的 server。只不过这个 jdwp 是jvm 内部的库,不需要额外的 so/dylib/dll 文件。FE528资讯网——每日最新资讯28at.com

如有需要,你完全可以弄个 “datupiao” 的 agentlib,”到这个 jvm 上,然后在这个 lib 里调用 JVMTI 接口,然后暴露个端口提供服务和远程交互,实现自己的 jdwp!FE528资讯网——每日最新资讯28at.com

可能某些老板们注意到了,本地调试还要127.0.0.1走tcp 交互一遍,那远程调试呢?FE528资讯网——每日最新资讯28at.com

基于上面的解释,本地调试和远程调试真的没啥区别!或者说,在目前 IDEA/Eclipse 的实现下,不存在本地调试,都是远程!只不过一个是 127.0.0.1,一个是远程的 IP 而已。FE528资讯网——每日最新资讯28at.com

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

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

瞅瞅, VirtualMachine 里的就这点方法,能力上比 JVMTI 还是差远了FE528资讯网——每日最新资讯28at.com

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 大法就能直接用。FE528资讯网——每日最新资讯28at.com

FE528资讯网——每日最新资讯28at.com

-agentlib 和 -javaagent

有些细心的同学可能发现了,IDEA 默认的启动脚本里,同时配置了 -agentlib 和 -javaagent。FE528资讯网——每日最新资讯28at.com

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

这个 debugger-agent吧,其实也没干啥事,只是对 JDK 内置的一些线程做了些增强,辅助 IDEA 的 debug 功能,支持一些异步的调试。FE528资讯网——每日最新资讯28at.com

FE528资讯网——每日最新资讯28at.com

agentlib、javaagent 这俩兄弟,定位其实很像,都是加载自定义的代码。FE528资讯网——每日最新资讯28at.com

不过区别在于,agentlib 是加载 native lib,需要c/cpp 去写,相当于自己的代码在 jvm 上,可以为所欲为,比如在 agentlib 里调用上面说的 JVMTI 。FE528资讯网——每日最新资讯28at.com

而 javaagent 是用 java 写的,可以直接用上层的 Instrumentation API,做一些类的增强转换之类,这也是大多数 APM Agent、Profiler Agent实现的基本原理。FE528资讯网——每日最新资讯28at.com

Arthas 的玩法

Arthas 的核心入口,其实还是 javaagent,支持静态加载和动态加载两种玩法。FE528资讯网——每日最新资讯28at.com

静态没啥好说的,启动脚本里增加一个-javaagent:/tmp/test/arthas-agent.jar,然后为所欲为。FE528资讯网——每日最新资讯28at.com

动态的叫 attach,使用 Java 提供的 VirtualMachine 就可以实现运行时添加 -javaagent,效果一样:FE528资讯网——每日最新资讯28at.com

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

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

注意到了吗?Arthas 可以直接下载一个 jar 包,java -jar 就能连上。FE528资讯网——每日最新资讯28at.com

其实吧,它这个直接启动的 jar 包,是一个 boot 包,启动之后把乱七八糟的 jar 都下载下来。接着动态 attach 的方式,连接到本机指定进程号的 JVM,然后再为所欲为。FE528资讯网——每日最新资讯28at.com

在 3.5 版本之后,Arthas 还新增了一个 vmtool 命令,这个命令可以直接获取内存中的指定对象实例。FE528资讯网——每日最新资讯28at.com

$ 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 的能力,这下是真的为所欲为了!FE528资讯网——每日最新资讯28at.com

#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 JNICALLJava_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!%u/n", error);        return NULL;    }    jint count = 0;    jobject *instances;    error = jvmti->GetObjectsWithTags(1, &tag, &count, &instances, NULL);    if (error) {        printf("ERROR: JVMTI GetObjectsWithTags failed!%u/n", 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;}

总结

  1. Debug 基于 JDPA 体系

IDE 直接接入 JDPA 体系中的 JDI 接口完成FE528资讯网——每日最新资讯28at.com

JDI 通过 JDWP 协议,调用远程 VM 的 JVMTI 接口FE528资讯网——每日最新资讯28at.com

JDWP 是通过 agentlib 加载的,agentlib 算是一个 native 的静态接口FE528资讯网——每日最新资讯28at.com

  1. javaagent 是 JAVA 层面的接口,用过 Instrumentation API(Java)实现各种功能,主要用于APM、Profiler 工具
  2. 如果你想,在 javaagent 里调用功能更丰富的 JVMTI 也不是不行。

本文链接:http://www.28at.com/showinfo-26-56602-0.html反向 Debug 了解一下?揭秘 Java DEBUG 的基本原理

声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com

上一篇: 深入了解服务器 CPU 的型号、代际、片内与片间互联架构

下一篇: Vue3问题:如何实现密码加密登录?前后端!

标签:
  • 热门焦点
  • vivo TWS Air开箱体验:真轻 臻好听

    在vivo S15系列新机的发布会上,vivo的最新款真无线蓝牙耳机vivo TWS Air也一同发布,本次就这款耳机新品给大家带来一个简单的分享。外包装盒上,vivo TWS Air保持了vivo自家产
  • 《英雄联盟》夏季赛总决赛今日开打!JDG对阵LNG首发名单来了 Knight:准备三连冠

    8月5日消息,今日17:00,《英雄联盟》2023LPL夏季赛总决赛将正式开打,由JDG对阵LNG。对两支队伍来说,这场比赛不仅要争夺夏季赛冠军,更要决定谁才是LPL赛区一
  • K6:面向开发人员的现代负载测试工具

    K6 是一个开源负载测试工具,可以轻松编写、运行和分析性能测试。它建立在 Go 和 JavaScript 之上,它被设计为功能强大、可扩展且易于使用。k6 可用于测试各种应用程序,包括 Web
  • 掘力计划第 20 期:Flutter 混合开发的混乱之治

    在掘力计划系列活动第20场,《Flutter 开发实战详解》作者,掘金优秀作者,Github GSY 系列目负责人恋猫的小郭分享了Flutter 混合开发的混乱之治。Flutter 基于自研的 Skia 引擎
  • 2023 年的 Node.js 生态系统

    随着技术的不断演进和创新,Node.js 在 2023 年达到了一个新的高度。Node.js 拥有一个庞大的生态系统,可以帮助开发人员更快地实现复杂的应用。本文就来看看 Node.js 最新的生
  • 如何正确使用:Has和:Nth-Last-Child

    我们可以用CSS检查,以了解一组元素的数量是否小于或等于一个数字。例如,一个拥有三个或更多子项的grid。你可能会想,为什么需要这样做呢?在某些情况下,一个组件或一个布局可能会
  • 量化指标是与非:挽救被量化指标扼杀的技术团队

    作者 | 刘新翠整理 | 徐杰承本文整理自快狗打车技术总监刘新翠在WOT2023大会上的主题分享,更多精彩内容及现场PPT,请关注51CTO技术栈公众号,发消息【WOT2023PPT】即可直接领取
  • ESG的面子与里子

    来源 | 光子星球撰文 | 吴坤谚编辑 | 吴先之三伏大幕拉起,各地高温预警不绝,但处于厄尔尼诺大&ldquo;烤&rdquo;之下的除了众生,还有各大企业发布的ESG报告。ESG是&ldquo;环境保
  • onebot M24巧系列一体机采用轻薄机身设计,现已在各平台开售

    onebot M24 巧系列一体机目前已在线上线下各平台同步开售。onebot M24 巧系列采用一体化轻薄机身设计,最薄处为 10.15mm,拥有宝石红、午夜蓝、石墨绿、雅致
Top