Byte Buddy Advice ломает отладчик Eclipse - PullRequest
0 голосов
/ 29 августа 2018

Я использовал 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");
    }

}

Call Byte Buddy:

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

И в представлении «Переменные» написано: 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");
}

Ява дает:

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;

Diff: enter image description here

Может быть, это ошибка, которую отладчик Eclipse ожидает, что this всегда будет в слоте 0? Или, может быть, так и должно быть. Код ошибки 35 исходит из JVM.

Причина, по которой добавление рекомендаций по выходу приводит к изменению слотов, заключается в том, что вместо Simple используется ForInstrumentedMethod.Default.Copying. И у них есть различные реализации variable().

Ответы [ 2 ]

0 голосов
/ 03 сентября 2018

Обход

Aha! Вся моя рыбалка вокруг в поисках патча привела меня к тому, что я хотел бы найти раньше.

  • Я могу использовать @OnMethodEnter(prependLineNumber = false), чтобы избежать проблемы с номерами строк.

  • А использование @OnMethodExit(backupArguments = false) позволяет избежать проблем со слотами.

Это хорошая новость для меня! Однако, по-видимому, это не значения по умолчанию по уважительной причине. Я пока не понимаю, есть ли важные негативные последствия использования этих опций.

0 голосов
/ 29 августа 2018

См. Ошибка Eclipse 531706 :

Проблема возникает, когда инструментированы не все классы , см. комментарий № 4 Тобиаса Хирнинга :

...

Теперь я также получаю более четкую картину: ошибки появились только в вызовы методов, где методы находятся в jar-файлах. И я думаю они не были оснащены.

...

Ошибка происходит в ВМ, а не в Eclipse . Когда Eclipse запрашивает переменные через интерфейс отладки, вместо значений возвращается код ошибки 35. Изменение, внесенное из-за упомянутого отчета об ошибке, должно игнорировать его, см. комментарий № 7 Тилла Бричи (который сделал изменение) :

...

Мне удалось воспроизвести проблему и просто игнорировать InternalException в этом кодовом пути улучшает ситуацию.

Иногда вы увидите сообщение о коде ошибки 35 в Переменные видят, но в целом это похоже на работу.

Чтобы избежать этой проблемы, вы должны использовать все классы.

...