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

作者:男性 来源:最新热点 浏览: 【 】 发布时间:2025-05-26 02:01:15 评论数:

Debug 的时分,都遇到过手速太快,直接越过了自己想调试的办法、代码的时分吧……。

一旦越过,或许就得从头履行一遍,预备数据、从头发动或许几分钟就过去了。

好在IDE 们都很强壮,还给你懊悔的时机,能够直接删去某个 St。ac。k Frame,直接回来到之前的状况,切当的说是回来到之前的某个 Stack Frame,然后完结让程序“逆向运转”。

这个 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 Platf。or。m Debugger Archi。te。cture),这个 JPDA 架构系统由 3 部分组成:

JVM。 TI。- Java VM Tool Interface。

JDWP - Java Debug Wire Protocol。

JDI - Java Debug Interface。

假如结合IDE 来看,那么一个完好的 Debug 功用看起来便是这个姿态:

解说一下这个系统:

JVM TI 是一个 JVM 供给的一个调试。接口。,供给了一系列操控 JVM 行为的功用,比方剖析、调试、监控、线程剖析等等。也便是说,这个接口界说了一系列调试剖析功用,而 JVM 完结了这个接口,然后供给调试才能。

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

JDI 接口运用 JDWP 这个私有的应用层协议,经过 TCP 和方针 VM 的 JVMTI 接口进行交互。

也能够把简略这个 JDWP 协议理解为 JSF/Dubbo 协议;相当于 IDE 里经过 JDI 这个。 SD。K,运用 JDWP 协议调用长途 JVMTI 的 RPC 接口,来传输调试时的各种断点、检查操作。

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

当然能够,比方 Arthas 里的部分功用,就直接运用了 JVMTI 接口,要什么 JDI!直接 JVMTI 干就完了。

开个打趣,Arthas 究竟不是 Debug 东西,人家底子就不必 JDI 接口。并且 JVMTI 的才能也不仅仅断点,它的功用十分多:

左面的功用类,供给了各种杂乱无章的功用,比方咱们常用的增加一个断点:

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

右边的事情类,能够简略的理解为回调;仍是拿断点举例,假如我用上面的 SetBreakpoint 增加了一个断点,那么当履行到该方位时,就会触发这个事情:。

void JN。IC。ALLBreakpoint(jvmtiEnv *jvmti_env,            JNIEnv* jni_env,            jthre。ad。thread,            jmethodID method,            jlocation location)。

JVMTI 的功用十分之多,而 JDI 仅仅完结了部分 JVMTI 的办法,所以某些专业的 Profiler 东西,或许会直接运用 JVMTI,然后完结更丰厚的确诊剖析功用。

长途调试与本地调试。

不知道咱们有没有留心过本地 Debug 发动时的日志:

榜首行是躲藏了后半段的发动指令,打开后是这个姿态:

/path/to/java -agentlib:jdwp=transport=dt_socket,address=127.0.0.1:53631,suspend=y,server=n -javaagent:/path/to/jetbr。ai。ns/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 上,下面是“外挂”的参数格局:

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

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

如有需求,你完全能够弄个 “datu。pi。ao” 的 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 大法就能直接用。

-agentlib 和 -javaagent。

有些仔细的同学或许发现了,IDEA 默许的发动脚本里,一起装备了 -agentlib 和 -javaagent。

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

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

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,然后随心所欲。

动态的叫 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 10String[][    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 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 fai。led。!%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 也不是不可。

审阅修改 黄宇。

内容来源:https://havascm.com/app-1/kết quả cúp nhà vua tây ban nha,http://chatbotjud.saude.mg.gov.br/app-1/mae-gostosa

系统发生错误

系统发生错误

您可以选择 [ 重试 ] [ 返回 ] 或者 [ 回到首页 ]

[ 错误信息 ]

页面发生异常错误,系统设置开启调试模式后,刷新本页查看具体错误!