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;

Diff:

Может быть, это ошибка, которую ожидает отладчик 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) избегает проблемы со слотами.

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

Другие вопросы по тегам