我已经使用net.bytebuddy.asm.Advice在适当注释的方法之前和之后添加代码,以启动和停止计时器。修改后的类会在引用它们的原始文件之前手动加载到目标类加载器中,从而取代它们。我正在使用OSGi(Equinox)。
非常好,但是当我在目标方法的断点上停止Eclipse(Photon 4.8.0)调试器时,Variables视图仅显示:
com.sun.jdi.InternalException:收到错误代码:35从堆栈框架中检索'this'。
这是不可避免的,也是不可避免的吗?有点废弃我的用例,如果这使得检测代码不可判坏:(
(我已禁用选项“在步骤操作后显示方法结果(如果VM支持;可能很慢”。)
我相信我可能已经发现了生成的字节码的一些问题。
要检验的类:
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");
}
}
打电话给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;
}
如果我在Test.java的第11行设置了一个断点,那么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.
如果我破解字节码在0x2A2将00改为0B,那么行号表如下所示:
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()
实现。
如果并非所有类都已经过检测,则会出现此问题,请参阅comment #4 by Tobias Hirning:
...
现在我也得到了一个更清晰的图片:错误只出现在方法调用中,方法在jar文件中。我认为他们没有装备。
...
错误发生在VM中,而不是在Eclipse中。当Eclipse通过调试接口请求变量时,将返回错误代码35
而不是值。由于提到的错误报告而做出的更改是忽略它,请参阅comment #7 by Till Brychcy (who made the change):
...
我已经能够重现问题,只是忽略此代码路径中的InternalException改善了这种情况。
您有时会在变量视图中看到有关错误代码35的消息,但通常它似乎有效。
要避免此问题,您必须检测所有类。
啊哈!所有我想去修补的钓鱼都让我找到了我希望我早点找到的东西。
@OnMethodEnter(prependLineNumber = false)
来避免行号的问题。@OnMethodExit(backupArguments = false)
避免了插槽的问题。这对我来说是个好消息!但是,大概这些并不是有充分理由的默认值。我还不知道使用这些选项是否会产生重大的负面影响。