是否可以在被调试端获取Java中JDI当前的StackFrame?
Is it possible to get JDI's current StackFrame in Java at the debuggee side?
所以,JDI
允许我们在被调试应用程序中设置一个断点,然后通过JDWP
获取当前的StackFrame
。据我了解,JVMTI
用于被调试端通过JDWP
.
将请求的信息发送到JDI
是否有可能从被调试者本身获取当前的 StackFrame
(因此无需将其发送给调试器...被调试者将是它自己的调试器)?
例如考虑这段代码:
//client code
int a = 5;
StackFrame frame = ...
//list will contain variable "a"
List<LocalVariable> visibleVariables = frame.visibleVariables();
有可能,但有一些问题。
必须已在启动时为 JVM 启用调试。要连接您自己的 JVM,您需要使用应用程序知道的预定义端口或附加功能,这需要为最近的 JVM 显式启用自附加。
那么,既然你要挂起你要检查的线程,它就不能是执行检查的同一个线程。所以你必须将任务委托给不同的线程。
例如
public static void main(String[] args) throws Exception {
Object o = null;
int test = 42;
String s = "hello";
Map<String, Object> vars = variables();
System.out.println(vars);
}
// get the variables in the caller’s frame
static Map<String,Object> variables() throws Exception {
Thread th = Thread.currentThread();
String oldName = th.getName(), tmpName = UUID.randomUUID().toString();
th.setName(tmpName);
long depth = StackWalker.getInstance(
StackWalker.Option.SHOW_HIDDEN_FRAMES).walk(Stream::count) - 1;
ExecutorService es = Executors.newSingleThreadExecutor();
try {
return es.<Map<String,Object>>submit(() -> {
VirtualMachineManager m = Bootstrap.virtualMachineManager();
for(var ac: m.attachingConnectors()) {
Map<String, Connector.Argument> arg = ac.defaultArguments();
Connector.Argument a = arg.get("pid");
if(a == null) continue;
a.setValue(String.valueOf(ProcessHandle.current().pid()));
VirtualMachine vm = ac.attach(arg);
return getVariableValues(vm, tmpName, depth);
}
return Map.of();
}).get();
} finally {
th.setName(oldName);
es.shutdown();
}
}
private static Map<String,Object> getVariableValues(
VirtualMachine vm, String tmpName, long depth)
throws IncompatibleThreadStateException, AbsentInformationException {
for(ThreadReference r: vm.allThreads()) {
if(!r.name().equals(tmpName)) continue;
r.suspend();
try {
StackFrame frame = r.frame((int)(r.frameCount() - depth));
return frame.getValues(frame.visibleVariables())
.entrySet().stream().collect(HashMap::new,
(m,e) -> m.put(e.getKey().name(), t(e.getValue())), Map::putAll);
} finally {
r.resume();
}
}
return Map.of();
}
private static Object t(Value v) {
if(v == null) return null;
switch(v.type().signature()) {
case "Z": return ((PrimitiveValue)v).booleanValue();
case "B": return ((PrimitiveValue)v).byteValue();
case "S": return ((PrimitiveValue)v).shortValue();
case "C": return ((PrimitiveValue)v).charValue();
case "I": return ((PrimitiveValue)v).intValue();
case "J": return ((PrimitiveValue)v).longValue();
case "F": return ((PrimitiveValue)v).floatValue();
case "D": return ((PrimitiveValue)v).doubleValue();
case "Ljava/lang/String;": return ((StringReference)v).value();
}
if(v instanceof ArrayReference)
return ((ArrayReference)v).getValues().stream().map(e -> t(e)).toArray();
return v.type().name()+'@'+Integer.toHexString(v.hashCode());
}
当我 运行 在我的机器上 JDK 12 使用选项
-Djdk.attach.allowAttachSelf -agentlib:jdwp=transport=dt_socket,server=y,suspend=n
,它打印
Listening for transport dt_socket at address: 50961
{args=[Ljava.lang.Object;@146ba0ac, s=hello, test=42, o=null}
所以,JDI
允许我们在被调试应用程序中设置一个断点,然后通过JDWP
获取当前的StackFrame
。据我了解,JVMTI
用于被调试端通过JDWP
.
JDI
是否有可能从被调试者本身获取当前的 StackFrame
(因此无需将其发送给调试器...被调试者将是它自己的调试器)?
例如考虑这段代码:
//client code
int a = 5;
StackFrame frame = ...
//list will contain variable "a"
List<LocalVariable> visibleVariables = frame.visibleVariables();
有可能,但有一些问题。
必须已在启动时为 JVM 启用调试。要连接您自己的 JVM,您需要使用应用程序知道的预定义端口或附加功能,这需要为最近的 JVM 显式启用自附加。
那么,既然你要挂起你要检查的线程,它就不能是执行检查的同一个线程。所以你必须将任务委托给不同的线程。
例如
public static void main(String[] args) throws Exception {
Object o = null;
int test = 42;
String s = "hello";
Map<String, Object> vars = variables();
System.out.println(vars);
}
// get the variables in the caller’s frame
static Map<String,Object> variables() throws Exception {
Thread th = Thread.currentThread();
String oldName = th.getName(), tmpName = UUID.randomUUID().toString();
th.setName(tmpName);
long depth = StackWalker.getInstance(
StackWalker.Option.SHOW_HIDDEN_FRAMES).walk(Stream::count) - 1;
ExecutorService es = Executors.newSingleThreadExecutor();
try {
return es.<Map<String,Object>>submit(() -> {
VirtualMachineManager m = Bootstrap.virtualMachineManager();
for(var ac: m.attachingConnectors()) {
Map<String, Connector.Argument> arg = ac.defaultArguments();
Connector.Argument a = arg.get("pid");
if(a == null) continue;
a.setValue(String.valueOf(ProcessHandle.current().pid()));
VirtualMachine vm = ac.attach(arg);
return getVariableValues(vm, tmpName, depth);
}
return Map.of();
}).get();
} finally {
th.setName(oldName);
es.shutdown();
}
}
private static Map<String,Object> getVariableValues(
VirtualMachine vm, String tmpName, long depth)
throws IncompatibleThreadStateException, AbsentInformationException {
for(ThreadReference r: vm.allThreads()) {
if(!r.name().equals(tmpName)) continue;
r.suspend();
try {
StackFrame frame = r.frame((int)(r.frameCount() - depth));
return frame.getValues(frame.visibleVariables())
.entrySet().stream().collect(HashMap::new,
(m,e) -> m.put(e.getKey().name(), t(e.getValue())), Map::putAll);
} finally {
r.resume();
}
}
return Map.of();
}
private static Object t(Value v) {
if(v == null) return null;
switch(v.type().signature()) {
case "Z": return ((PrimitiveValue)v).booleanValue();
case "B": return ((PrimitiveValue)v).byteValue();
case "S": return ((PrimitiveValue)v).shortValue();
case "C": return ((PrimitiveValue)v).charValue();
case "I": return ((PrimitiveValue)v).intValue();
case "J": return ((PrimitiveValue)v).longValue();
case "F": return ((PrimitiveValue)v).floatValue();
case "D": return ((PrimitiveValue)v).doubleValue();
case "Ljava/lang/String;": return ((StringReference)v).value();
}
if(v instanceof ArrayReference)
return ((ArrayReference)v).getValues().stream().map(e -> t(e)).toArray();
return v.type().name()+'@'+Integer.toHexString(v.hashCode());
}
当我 运行 在我的机器上 JDK 12 使用选项
-Djdk.attach.allowAttachSelf -agentlib:jdwp=transport=dt_socket,server=y,suspend=n
,它打印
Listening for transport dt_socket at address: 50961
{args=[Ljava.lang.Object;@146ba0ac, s=hello, test=42, o=null}