Можно ли получить текущий StackFrame JDI в Java на стороне отлаживаемого? - PullRequest
2 голосов
/ 17 июня 2020

Итак, JDI позволяет нам установить точку останова в отлаживаемом приложении, а затем получить текущую StackFrame через JDWP. Насколько я понимаю, JVMTI используется на стороне отлаживаемого ПО для отправки запрошенной информации на JDI через JDWP.

Возможно ли получить текущий StackFrame из самого отлаживаемого ПО (так что без отправив его отладчику ... отладчик будет собственным отладчиком)?

Например, рассмотрим этот код:

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

//list will contain variable "a"
List<LocalVariable> visibleVariables = frame.visibleVariables();

1 Ответ

4 голосов
/ 19 июня 2020

Возможно, с некоторыми ограничениями.

Отладка должна быть включена для 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}
...