java debug初探

栏目: Java · 发布时间: 5年前

内容简介:关于Java debug架构,有一堆相关的名词。其中JPDA是整个debug架构的缩写:Java Platform Debugger Architecture,整个架构可以从从图中可以看出,

JPDA、JDI、JDWP傻傻分不清楚

关于Java debug架构,有一堆相关的名词。其中JPDA是整个debug架构的缩写:Java Platform Debugger Architecture,

整个架构可以从 JPDA文档 最开头了解到:

Components                         Debugger Interfaces

               /    |--------------|
              /     |     VM       |
debuggee ----(      |--------------|  <------- JVMTI - Java VM Tool Interface
              \     |   back-end   |
               \    |--------------|
               /           |
comm channel -(            |  <--------------- JDWP - Java Debug Wire Protocol
               \           |
                    |--------------|
                    | front-end    |
                    |--------------|  <------- JDI - Java Debug Interface
                    |      UI      |
                    |--------------|

从图中可以看出,

  • JDI: Java Debug Interface,作为整个debug架构的客户端API,封装了一些Java API用于debug。包括连接JVM,断点等等功能。
  • JDWP:Java Debug Wire Protocol,它实际是一个传输协议,定义了debug客户端和被debug的JVM之间的通信协议。具体协议参见 文档
  • JVMTI:前文已经介绍过,这是JVM提供的扩展接口。

JDI如何工作

JDI定义了一堆接口,用于实现整个Debug功能。JDI接口文档在: https://docs.oracle.com/javase/8/docs/jdk/api/jpda/jdi/index.html

JDI中最核心的的类是 VirtualMachine ,提供连接目标机器debug端口,以及一堆支持的指令。注意tools.jar中包含2个同名对象,JDI相关的在jdi包中。

JDI使用

public class TempAgent {
    static VirtualMachine vm;
    public static void main(String[] args) throws IOException, IllegalConnectorArgumentsException {
        attach();

        //获取对象
        obtainTempTestObj();
    }

    private static void obtainTempTestObj() {
        List<ReferenceType> referenceTypes = vm.classesByName("TempTest");
        for (ReferenceType referenceType : referenceTypes) {
            List<ObjectReference> instances = referenceType.instances(0L);
            for (ObjectReference instance : instances) {
                System.out.println(instance);
            }
        }
    }

    private static void attach() throws IOException, IllegalConnectorArgumentsException {
        // 一、取得连接器
        VirtualMachineManager vmm = Bootstrap.virtualMachineManager();
        List<AttachingConnector> connectors = vmm.attachingConnectors();
        SocketAttachingConnector sac = null;
        for (AttachingConnector ac : connectors) {
            if (ac instanceof SocketAttachingConnector) {
                sac = (SocketAttachingConnector) ac;
                break;
            }
        }
        if (sac == null) {
            System.out.println("JDI error");
            return;
        }

        // 二、连接到远程虚拟器
        Map<String, Argument> arguments = sac.defaultArguments();
        Connector.Argument hostArg = (Connector.Argument) arguments.get("hostname");
        Connector.Argument portArg = (Connector.Argument) arguments.get("port");

        // hostArg.setValue("127.0.0.1");
        portArg.setValue(String.valueOf(8800));

        vm = sac.attach(arguments);
        vm.process();
    }
}

这里通过JDI接口,实现了通过debug端口连接到目标JVM,并获取一个类的对象引用。这里我们来看下实际获取对象引用的方式。

JDI实现

JDI实现代码在 jdk/src/share/classes/com/sun/tools/jdi/VirtualMachineImpl.java 类中,

对于本文关注功能(获得对象引用),代码在 jdk/src/share/classes/com/sun/tools/jdi/ReferenceTypeImpl.java

其中获取引用的代:

public List<ObjectReference> instances(long maxInstances) {
    if (!vm.canGetInstanceInfo()) {
        throw new UnsupportedOperationException(
            "target does not support getting instances");
    }

    if (maxInstances < 0) {
        throw new IllegalArgumentException("maxInstances is less than zero: "
                                          + maxInstances);
    }
    int intMax = (maxInstances > Integer.MAX_VALUE)?
        Integer.MAX_VALUE: (int)maxInstances;
    // JDWP can't currently handle more than this (in mustang)

    try {
        return Arrays.asList(
            (ObjectReference[])JDWP.ReferenceType.Instances.
                    process(vm, this, intMax).instances);
    } catch (JDWPException exc) {
        throw exc.toJDIException();
    }
}

这里可以看见,所有实际操作,都在JDWP这个类中。但是如果直接搜索这个类名,可以发现JDK源码中不包含这个类的源码。

实际这个类是构建时生成的,生成规则在 jdk/make/gensrc/GensrcJDWP.gmk 中:

$(JDK_OUTPUTDIR)/gensrc/com/sun/tools/jdi/JDWP.java: $(JDWP_SPEC_FILE)
        $(MKDIR) -p $(@D)
        $(MKDIR) -p $(JDK_OUTPUTDIR)/gensrc_jdwp_headers
        $(RM) $@ $(JDK_OUTPUTDIR)/gensrc_jdwp_headers/JDWPCommands.h
        $(ECHO) $(LOG_INFO) Creating JDWP.java and JDWPCommands.h from jdwp.spec
        $(TOOL_JDWPGEN) $< -jdi $@ -include $(JDK_OUTPUTDIR)/gensrc_jdwp_headers/JDWPCommands.h

其中 TOOL_JDWPGEN 变量在 Tools.gmk 文件中定义:

TOOL_JDWPGEN = $(JAVA) -cp $(JDK_OUTPUTDIR)/btclasses build.tools.jdwpgen.Main

实际会通过JDWP协议和目标JVM交互。

目标JVM实现

每个JDI接口,基本上都有一个对应的实现。所有JDWP后端实现都在 jdk/src/share/back 目录中。

对于获取引用的功能,实现在 ReferenceTypeImpl.c 文件中:

static jboolean
instances(PacketInputStream *in, PacketOutputStream *out)
{
    jint maxInstances;
    jclass clazz;
    JNIEnv *env;

    if (gdata->vmDead) {
        outStream_setError(out, JDWP_ERROR(VM_DEAD));
        return JNI_TRUE;
    }

    env = getEnv();
    clazz = inStream_readClassRef(env, in);
    maxInstances = inStream_readInt(in);
    if (inStream_error(in)) {
        return JNI_TRUE;
    }

    WITH_LOCAL_REFS(env, 1) {
        jvmtiError   error;
        ObjectBatch  batch;

        error = classInstances(clazz, &batch, maxInstances);
        if (error != JVMTI_ERROR_NONE) {
            outStream_setError(out, map2jdwpError(error));
        } else {
            int kk;
            jbyte typeKey;

            (void)outStream_writeInt(out, batch.count);
            if (batch.count > 0) {
                /*
                 * They are all instances of this class and will all have
                 * the same typeKey, so just compute it once.
                 */
                typeKey = specificTypeKey(env, batch.objects[0]);

                for (kk = 0; kk < batch.count; kk++) {
                  jobject inst;

                  inst = batch.objects[kk];
                  (void)outStream_writeByte(out, typeKey);
                  (void)outStream_writeObjectRef(env, out, inst);
                }
            }
            jvmtiDeallocate(batch.objects);
        }
    } END_WITH_LOCAL_REFS(env);

    return JNI_TRUE;
}

这里最核心的函数调用是 classInstances ,这个函数在 util.c 文件中。

/* Get instances for one class */
jvmtiError
classInstances(jclass klass, ObjectBatch *instances, int maxInstances)
{
    ClassInstancesData data;
    jvmtiHeapCallbacks heap_callbacks;
    jvmtiError         error;
    jvmtiEnv          *jvmti;

    /* Check interface assumptions */

    if (klass == NULL) {
        return AGENT_ERROR_INVALID_OBJECT;
    }

    if ( maxInstances < 0 || instances == NULL) {
        return AGENT_ERROR_ILLEGAL_ARGUMENT;
    }

    /* Initialize return information */
    instances->count   = 0;
    instances->objects = NULL;

    /* Get jvmti environment to use */
    jvmti = getSpecialJvmti();
    if ( jvmti == NULL ) {
        return AGENT_ERROR_INTERNAL;
    }

    /* Setup data to passed around the callbacks */
    data.instCount    = 0;
    data.maxInstances = maxInstances;
    data.objTag       = (jlong)1;
    data.error        = JVMTI_ERROR_NONE;

    /* Clear out callbacks structure */
    (void)memset(&heap_callbacks,0,sizeof(heap_callbacks));

    /* Set the callbacks we want */
    heap_callbacks.heap_reference_callback = &cbObjectTagInstance;

    /* Follow references, no initiating object, just this class, all objects */
    error = JVMTI_FUNC_PTR(jvmti,FollowReferences)
                 (jvmti, 0, klass, NULL, &heap_callbacks, &data);
    if ( error == JVMTI_ERROR_NONE ) {
        error = data.error;
    }

    /* Get all the instances now that they are tagged */
    if ( error == JVMTI_ERROR_NONE ) {
        error = JVMTI_FUNC_PTR(jvmti,GetObjectsWithTags)
                      (jvmti, 1, &(data.objTag), &(instances->count),
                       &(instances->objects), NULL);
        /* Verify we got the count we expected */
        if ( data.instCount != instances->count ) {
            error = AGENT_ERROR_INTERNAL;
        }
    }
    /* Dispose of any special jvmti environment */
    (void)JVMTI_FUNC_PTR(jvmti,DisposeEnvironment)(jvmti);
    return error;
}

除了开头一些准备工作,这里实际的调用使用了jvmti的 FollowReferencesGetObjectsWithTags

两个函数。第一个函数用于在堆中标记期望的对象,第二个函数从堆中将所有做了标记的对象取出来。

因此,实际上JDWP后端,最终通过jvmti实现了一个agent,通过jvmti的API对外提供服务。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Learning Python, 5th Edition

Learning Python, 5th Edition

Mark Lutz / O'Reilly Media / 2013-7-6 / USD 64.99

If you want to write efficient, high-quality code that's easily integrated with other languages and tools, this hands-on book will help you be productive with Python quickly. Learning Python, Fifth Ed......一起来看看 《Learning Python, 5th Edition》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

随机密码生成器
随机密码生成器

多种字符组合密码

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具