是否可以在调试端获取JDI当前Java中的StackFrame?

Nff*_*ff3 2 java debugging breakpoints jdi jvmti

因此,JDI允许我们在调试对象应用程序中设置断点,然后StackFrame通过获取当前断点JDWP。据我了解,JVMTI用于在调试者端将请求的信息发送到JDIthrough JDWP

是否有可能从被调试者本身获取电流StackFrame(因此无需将其发送到调试器......被调试者将是它自己的调试器)?

例如考虑以下代码:

//client code
int a = 5;
StackFrame frame = ...

//list will contain variable "a"
List<LocalVariable> visibleVariables = frame.visibleVariables();
Run Code Online (Sandbox Code Playgroud)

Hol*_*ger 5

它\xe2\x80\x99是可能的,但有一些问题。

\n\n

必须在启动时已启用 JVM 的调试。要连接您自己的 JVM,您需要使用应用程序知道的预定义端口或附加功能,这需要为最新的 JVM 显式启用自附加。

\n\n

然后,由于您必须挂起要检查的线程,因此它不能是执行检查的同一个线程。所以你必须将任务委托给不同的线程。

\n\n

例如

\n\n
public static void main(String[] args) throws Exception {\n    Object o = null;\n    int test = 42;\n    String s = "hello";\n    Map<String, Object> vars = variables();\n    System.out.println(vars);\n}\n// get the variables in the caller\xe2\x80\x99s frame\nstatic Map<String,Object> variables() throws Exception {\n    Thread th = Thread.currentThread();\n    String oldName = th.getName(), tmpName = UUID.randomUUID().toString();\n    th.setName(tmpName);\n    long depth = StackWalker.getInstance(\n        StackWalker.Option.SHOW_HIDDEN_FRAMES).walk(Stream::count) - 1;\n\n    ExecutorService es = Executors.newSingleThreadExecutor();\n    try {\n        return es.<Map<String,Object>>submit(() -> {\n            VirtualMachineManager m = Bootstrap.virtualMachineManager();\n            for(var ac: m.attachingConnectors()) {\n                Map<String, Connector.Argument> arg = ac.defaultArguments();\n                Connector.Argument a = arg.get("pid");\n                if(a == null) continue;\n                a.setValue(String.valueOf(ProcessHandle.current().pid()));\n                VirtualMachine vm = ac.attach(arg);\n                return getVariableValues(vm, tmpName, depth);\n            }\n            return Map.of();\n        }).get();\n    } finally {\n        th.setName(oldName);\n        es.shutdown();\n    }\n}\n\nprivate static Map<String,Object> getVariableValues(\n        VirtualMachine vm, String tmpName, long depth)\n        throws IncompatibleThreadStateException, AbsentInformationException {\n\n    for(ThreadReference r: vm.allThreads()) {\n        if(!r.name().equals(tmpName)) continue;\n        r.suspend();\n        try {\n            StackFrame frame = r.frame((int)(r.frameCount() - depth));\n            return frame.getValues(frame.visibleVariables())\n                .entrySet().stream().collect(HashMap::new,\n                    (m,e) -> m.put(e.getKey().name(), t(e.getValue())), Map::putAll);\n        } finally {\n            r.resume();\n        }\n    }\n    return Map.of();\n}\nprivate static Object t(Value v) {\n    if(v == null) return null;\n    switch(v.type().signature()) {\n        case "Z": return ((PrimitiveValue)v).booleanValue();\n        case "B": return ((PrimitiveValue)v).byteValue();\n        case "S": return ((PrimitiveValue)v).shortValue();\n        case "C": return ((PrimitiveValue)v).charValue();\n        case "I": return ((PrimitiveValue)v).intValue();\n        case "J": return ((PrimitiveValue)v).longValue();\n        case "F": return ((PrimitiveValue)v).floatValue();\n        case "D": return ((PrimitiveValue)v).doubleValue();\n        case "Ljava/lang/String;": return ((StringReference)v).value();\n    }\n    if(v instanceof ArrayReference)\n        return ((ArrayReference)v).getValues().stream().map(e -> t(e)).toArray();\n    return v.type().name()+\'@\'+Integer.toHexString(v.hashCode());\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

当我使用选项
\n在带有 JDK\xc2\xa012 的计算机上运行此命令时-Djdk.attach.allowAttachSelf -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,它会打印

\n\n
public static void main(String[] args) throws Exception {\n    Object o = null;\n    int test = 42;\n    String s = "hello";\n    Map<String, Object> vars = variables();\n    System.out.println(vars);\n}\n// get the variables in the caller\xe2\x80\x99s frame\nstatic Map<String,Object> variables() throws Exception {\n    Thread th = Thread.currentThread();\n    String oldName = th.getName(), tmpName = UUID.randomUUID().toString();\n    th.setName(tmpName);\n    long depth = StackWalker.getInstance(\n        StackWalker.Option.SHOW_HIDDEN_FRAMES).walk(Stream::count) - 1;\n\n    ExecutorService es = Executors.newSingleThreadExecutor();\n    try {\n        return es.<Map<String,Object>>submit(() -> {\n            VirtualMachineManager m = Bootstrap.virtualMachineManager();\n            for(var ac: m.attachingConnectors()) {\n                Map<String, Connector.Argument> arg = ac.defaultArguments();\n                Connector.Argument a = arg.get("pid");\n                if(a == null) continue;\n                a.setValue(String.valueOf(ProcessHandle.current().pid()));\n                VirtualMachine vm = ac.attach(arg);\n                return getVariableValues(vm, tmpName, depth);\n            }\n            return Map.of();\n        }).get();\n    } finally {\n        th.setName(oldName);\n        es.shutdown();\n    }\n}\n\nprivate static Map<String,Object> getVariableValues(\n        VirtualMachine vm, String tmpName, long depth)\n        throws IncompatibleThreadStateException, AbsentInformationException {\n\n    for(ThreadReference r: vm.allThreads()) {\n        if(!r.name().equals(tmpName)) continue;\n        r.suspend();\n        try {\n            StackFrame frame = r.frame((int)(r.frameCount() - depth));\n            return frame.getValues(frame.visibleVariables())\n                .entrySet().stream().collect(HashMap::new,\n                    (m,e) -> m.put(e.getKey().name(), t(e.getValue())), Map::putAll);\n        } finally {\n            r.resume();\n        }\n    }\n    return Map.of();\n}\nprivate static Object t(Value v) {\n    if(v == null) return null;\n    switch(v.type().signature()) {\n        case "Z": return ((PrimitiveValue)v).booleanValue();\n        case "B": return ((PrimitiveValue)v).byteValue();\n        case "S": return ((PrimitiveValue)v).shortValue();\n        case "C": return ((PrimitiveValue)v).charValue();\n        case "I": return ((PrimitiveValue)v).intValue();\n        case "J": return ((PrimitiveValue)v).longValue();\n        case "F": return ((PrimitiveValue)v).floatValue();\n        case "D": return ((PrimitiveValue)v).doubleValue();\n        case "Ljava/lang/String;": return ((StringReference)v).value();\n    }\n    if(v instanceof ArrayReference)\n        return ((ArrayReference)v).getValues().stream().map(e -> t(e)).toArray();\n    return v.type().name()+\'@\'+Integer.toHexString(v.hashCode());\n}\n
Run Code Online (Sandbox Code Playgroud)\n

  • 我不这么认为。开发人员没有预见到应用程序调试本身,因此他们肯定没有针对这种情况实现任何优化。在我看来,attach API 的使用只是消除了事先知道端口号的需要,但最终会使用套接字通信。如果您的任务如问题中所描述的那样狭窄(获取您自己的当前堆栈帧),则字节码检测将允许更快的解决方案,而根本不需要 JVM 的调试功能。 (2认同)