Byte Buddy Advice ломает отладчик Eclipse
Я использовал net.bytebuddy.asm.Advice для добавления кода до и после соответствующим образом аннотированных методов, для запуска и остановки таймеров. Модифицированные классы загружаются вручную в загрузчик целевого класса, прежде чем можно будет ссылаться на их оригиналы, тем самым вытесняя их. Я использую OSGi (Equinox).
Довольно мило, но когда я останавливаю отладчик Eclipse (Photon 4.8.0) на точке останова в целевом методе, представление Variables показывает только:
com.sun.jdi.InternalException: получил код ошибки в ответе:35 произошло получение "this" из фрейма стека.
Это неизбежно и неизбежно? В некотором роде рушится мой сценарий использования, если это делает инструментальный код неисправимым:(
(Я отключил опцию "Показывать результат метода после пошаговой операции (если поддерживается виртуальной машиной; может быть медленной".)
пример
Я полагаю, что я мог найти некоторые проблемы с сгенерированным байт-кодом.
Класс для инструментария:
1 package com.tom.test;
2
3 import com.tom.instrument.Instrumented;
4 import com.tom.instrument.Timed;
5
6 @Instrumented(serviceType = "blah")
7 public class Test {
8
9 @Timed
10 public void writeName() {
11 final String myLocal = "Tom";
12 System.out.println(myLocal);
13 }
14
15 }
"Совет":
package com.tom.instrument;
import net.bytebuddy.asm.Advice.OnMethodEnter;
public class Instrumentation {
@OnMethodEnter
public static void onMethodEnter() {
System.out.println("Enter");
}
}
Вызов Байту Бадди:
new ByteBuddy()
.redefine(type, ClassFileLocator.ForClassLoader.of(this.classLoader))
.visit(Advice.to(Instrumentation.class)
.on(isAnnotatedWith(Timed.class)))
.make().saveIn(new File("instrumented"));
Результат в javap:
Compiled from "Test.java"
...
public void writeName();
Code:
0: getstatic #19 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #40 // String Enter
5: invokevirtual #25 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: goto 11
11: ldc #17 // String Tom
13: astore_1
14: getstatic #19 // Field java/lang/System.out:Ljava/io/PrintStream;
17: ldc #17 // String Tom
19: invokevirtual #25 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
22: return
LineNumberTable:
line 11: 0
line 12: 14
line 13: 22
LocalVariableTable:
Start Length Slot Name Signature
11 12 0 this Lcom/tom/test/Test;
14 9 1 myLocal Ljava/lang/String;
}
Если я установлю точку останова в строке 11 Test.java, то в представлении Eclipse Debug появится: <unknown receiving type>(Test).writeName() line: 11
И представление Variables говорит: com.sun.jdi.InternalException: Got error code in reply:35 occurred retrieving 'this' from stack frame.
Если я взломаю байт-код, меняющий 00 на 0B на 0x2A2, то таблица номеров строк будет выглядеть так:
LineNumberTable:
line 11: 11
line 12: 14
line 13: 22
То все в порядке! И мне это кажется правильным, но я здесь не эксперт.
Если я также использую @OnMethodExit
тоже тогда немного сложнее. Добавьте следующее к Instrumentation.class
:
@OnMethodExit
public static void onMethodExit() {
System.out.println("Exit");
}
javap дает:
Compiled from "Test.java"
...
public void writeName();
Code:
0: getstatic #19 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #40 // String Enter
5: invokevirtual #25 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: goto 11
11: aload_0
12: astore_1
13: ldc #17 // String Tom
15: astore_2
16: getstatic #19 // Field java/lang/System.out:Ljava/io/PrintStream;
19: ldc #17 // String Tom
21: invokevirtual #25 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
24: goto 27
27: getstatic #19 // Field java/lang/System.out:Ljava/io/PrintStream;
30: ldc #42 // String Exit
32: invokevirtual #25 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
35: goto 38
38: return
LineNumberTable:
line 11: 0
line 12: 16
line 13: 24
LocalVariableTable:
Start Length Slot Name Signature
13 14 1 this Lcom/tom/test/Test;
16 11 2 myLocal Ljava/lang/String;
}
Чтобы это исправить, мне нужно обновить таблицу номеров строк и таблицу локальных переменных. Как это:
LineNumberTable:
line 11: 13
line 12: 16
line 13: 24
LocalVariableTable:
Start Length Slot Name Signature
13 14 0 this Lcom/tom/test/Test;
16 11 1 myLocal Ljava/lang/String;
Может быть, это ошибка, которую ожидает отладчик Eclipse this
всегда быть в слоте 0? Или, может быть, так и должно быть. Код ошибки 35 исходит от JVM, хотя.
Причина, по которой добавление рекомендаций по выходу изменяет слоты, заключается в том, что ForInstrumentedMethod.Default.Copying
использоваться вместо Simple
, И у них есть разные реализации variable()
,
2 ответа
Смотрите Eclipse bug 531706:
Проблема возникает, когда не все классы были оснащены, см. Комментарий № 4 Тобиаса Хирнинга:
...
Теперь я также получаю более четкую картину: ошибки появляются только в вызовах методов, где методы находятся в jar-файлах. И я думаю, что они не были инструментированы.
...
Ошибка происходит в ВМ, а не в Eclipse. Когда Eclipse запрашивает переменные через код ошибки интерфейса отладки 35
возвращается вместо значений. Изменение, внесенное из-за упомянутого отчета об ошибке, должно игнорировать его, см. Комментарий № 7 Тилла Бричи (который сделал изменение):
...
Я смог воспроизвести проблему, и просто игнорирование InternalException в этом кодовом пути улучшает ситуацию.
Иногда вы увидите сообщение о коде ошибки 35 в представлении переменных, но в целом оно работает.
Чтобы избежать этой проблемы, вы должны использовать все классы.
Временное решение
Ага! Вся моя рыбалка вокруг в поисках патча привела меня к поиску чего-то, что я хотел бы найти раньше.
я могу использовать
@OnMethodEnter(prependLineNumber = false)
чтобы избежать проблемы с номерами строк.И используя
@OnMethodExit(backupArguments = false)
избегает проблемы со слотами.
Это хорошая новость для меня! Однако, по-видимому, это не значения по умолчанию по уважительной причине. Я пока не понимаю, есть ли важные негативные последствия использования этих опций.