我所做的是在运行时插入java类,用一个大的try-catch块扭曲整个方法,然后如果捕获到任何异常,则在catch块中重新抛出异常。
我使用一个Premain类添加一个类文件转换器来动态地插入加载的java类。变压器类别:
public class Transformer implements ClassFileTransformer {
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
ProtectionDomain protectionDomain, byte[] classfileBuffer) throws
IllegalClassFormatException {
byte[] result = classfileBuffer;
if (className == null){
return result;
}
...
try{
ClassReader cr = new ClassReader(result);
ClassWriter cw = new ClassWriter(cr, 0);
ClassVisitor cv = new CV(cw, className, loader, getClassVersion(cr));
cr.accept(cv, ClassReader.EXPAND_FRAMES);
result = cw.toByteArray();
} catch (Throwable t){
t.printStackTrace();
}
return result;
}
}
ClassVisitor和MethodVisitor:
public class CV extends ClassVisitor {
private String slashClassName;
private ClassLoader loader;
private int classVersion;
public CV(ClassVisitor classVisitor, String className, ClassLoader loader, int classVersion) {
super(ASM_Version, classVisitor);
this.slashClassName = className;
this.loader = loader;
this.classVersion = classVersion;
}
@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);
return new MV(mv, slashClassName, name, descriptor, this.classVersion);
}
}
class MV extends MethodVisitor {
...
// insert a try catch block for the whole test method to capture the exception thrown
private Label tryStart = new Label();
private Label tryEndCatchStart = new Label();
public MV(MethodVisitor methodVisitor, String className, String methodName, String desc, int classVersion) {
super(ASM_Version, methodVisitor);
...
}
@Override
public void visitCode() {
mv.visitCode();
mv.visitLabel(tryStart);
}
@Override
public void visitMaxs(int maxStack, int maxLocals) {
mv.visitTryCatchBlock(tryStart, tryEndCatchStart, tryEndCatchStart, "java/lang/Throwable");
mv.visitLabel(tryEndCatchStart);
mv.visitFrame(F_FULL, 0, null, 1, new Object[]{"java/lang/Throwable"});
mv.visitInsn(ATHROW);
mv.visitMaxs(maxStack+4, maxLocals);
}
}
但我遇到了这样的错误:
...
Caused by: java.lang.VerifyError: StackMapTable error: bad offset
Exception Details:
Location:
org/example/Test.personTest()V @0: new
Reason:
Invalid stackmap specification.
Current Frame:
bci: @147
flags: { }
locals: { }
stack: { 'java/lang/Throwable' }
Bytecode:
0x0000000: bb00 0259 bb00 0359 1204 1205 b700 06b7
0x0000010: 0007 b300 08b2 0008 bb00 0359 1209 120a
0x0000020: b700 06b5 000b b200 0cb2 000d b600 0eb2
0x0000030: 0008 b400 0b12 0fb5 0010 2ab7 0011 9900
0x0000040: 0bb2 000c 1212 b600 13b1 bf
Exception Handler Table:
bci [0, 74] => handler: 74
Stackmap Table:
same_frame_extended(@73)
full_frame(@147,{},{Object[#84]})
插入指令之前的原始类看起来像:
public void personTest();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=6, locals=1, args_size=1
0: new #2 // class org/example/Person
3: dup
4: new #3 // class org/example/Name
7: dup
8: ldc #4 // String xxx
10: ldc #5 // String yyy
12: invokespecial #6 // Method org/example/Name."<init>":(Ljava/lang/String;Ljava/lang/String;)V
15: invokespecial #7 // Method org/example/Person."<init>":(Lorg/example/Name;)V
18: putstatic #8 // Field president:Lorg/example/Person;
21: getstatic #8 // Field president:Lorg/example/Person;
24: new #3 // class org/example/Name
27: dup
28: ldc #9 // String a
30: ldc #10 // String b
32: invokespecial #6 // Method org/example/Name."<init>":(Ljava/lang/String;Ljava/lang/String;)V
35: putfield #11 // Field org/example/Person.name:Lorg/example/Name;
38: getstatic #12 // Field java/lang/System.out:Ljava/io/PrintStream;
41: getstatic #13 // Field name:Lorg/example/Name;
44: invokevirtual #14 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
47: getstatic #8 // Field president:Lorg/example/Person;
50: getfield #11 // Field org/example/Person.name:Lorg/example/Name;
53: ldc #15 // String c
55: putfield #16 // Field org/example/Name.familyName:Ljava/lang/String;
58: aload_0
59: invokespecial #17 // Method ifxxx:()Z
62: ifeq 73
65: getstatic #12 // Field java/lang/System.out:Ljava/io/PrintStream;
68: ldc #18 // String yes
70: invokevirtual #19 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
73: return
LineNumberTable:
line 33: 0
line 35: 21
line 36: 38
line 37: 47
line 38: 58
line 39: 65
line 41: 73
LocalVariableTable:
Start Length Slot Name Signature
0 74 0 this Lorg/example/Test;
StackMapTable: number_of_entries = 1
frame_type = 251 /* same_frame_extended */
offset_delta = 73
RuntimeVisibleAnnotations:
0: #40()
插入后的类看起来像:
public void personTest();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=10, locals=1, args_size=1
0: new #2 // class org/example/Person
3: dup
4: new #3 // class org/example/Name
7: dup
8: ldc #4 // String xxx
10: ldc #5 // String yyy
12: invokespecial #6 // Method org/example/Name."<init>":(Ljava/lang/String;Ljava/lang/String;)V
15: invokespecial #7 // Method org/example/Person."<init>":(Lorg/example/Name;)V
18: putstatic #8 // Field president:Lorg/example/Person;
21: getstatic #8 // Field president:Lorg/example/Person;
24: new #3 // class org/example/Name
27: dup
28: ldc #9 // String a
30: ldc #10 // String b
32: invokespecial #6 // Method org/example/Name."<init>":(Ljava/lang/String;Ljava/lang/String;)V
35: putfield #11 // Field org/example/Person.name:Lorg/example/Name;
38: getstatic #12 // Field java/lang/System.out:Ljava/io/PrintStream;
41: getstatic #13 // Field name:Lorg/example/Name;
44: invokevirtual #14 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
47: getstatic #8 // Field president:Lorg/example/Person;
50: getfield #11 // Field org/example/Person.name:Lorg/example/Name;
53: ldc #15 // String c
55: putfield #16 // Field org/example/Name.familyName:Ljava/lang/String;
58: aload_0
59: invokespecial #17 // Method ifxxx:()Z
62: ifeq 73
65: getstatic #12 // Field java/lang/System.out:Ljava/io/PrintStream;
68: ldc #18 // String yes
70: invokevirtual #19 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
73: return
74: athrow
Exception table:
from to target type
0 74 74 Class java/lang/Throwable
StackMapTable: number_of_entries = 2
frame_type = 251 /* same_frame_extended */
offset_delta = 73
frame_type = 255 /* full_frame */
offset_delta = 73
locals = []
stack = [ class java/lang/Throwable ]
LineNumberTable:
line 33: 0
line 35: 21
line 36: 38
line 37: 47
line 38: 58
line 39: 65
line 41: 73
LocalVariableTable:
Start Length Slot Name Signature
0 74 0 this Lorg/example/Test;
RuntimeVisibleAnnotations:
0: #40()
我发现StackMapTable中的两个帧具有相同的偏移量73!这是绝对错误的,因为
帧应用的字节码偏移量是通过将offset_delta+1添加到前一帧的字节码偏置量来计算的,除非前一帧是该方法的初始帧。。。
所以这就是它抱怨的原因。然而,我仍然不知道为什么插入的帧与现有帧具有相同的偏移量(不应该是0吗?(,这只是巧合吗?
您混合了两个不兼容的选项,EXPAND_FRAMES
和F_FULL
。来自visitFrame
:的文档
方法的帧必须以扩展形式或压缩形式给出(所有帧必须使用相同的格式,即不能在单个方法中混合扩展和压缩帧(:
- 在展开形式中,所有帧都必须具有
F_NEW
类型- 在压缩形式中,帧基本上是"压缩"的;Δ;从上一帧的状态:
Opcodes.F_SAME
表示具有与前一帧完全相同的局部区域并且具有空堆栈的帧Opcodes.F_SAME1
表示具有与前一帧完全相同的局部并且在堆栈上具有单个值的帧(numStack
是1,stack[0]
包含堆栈项的类型的值(- 除了定义了额外的局部(
numLocal
是1、2或3,局部元素包含表示添加的类型的值(之外,用当前局部表示帧的Opcodes.F_APPEND
与前一帧中的局部相同- 代表具有当前局部的帧的
Opcodes.F_CHOP
与前一帧中的局部相同,只是最后1-3个局部不存在并且具有空堆栈(numLocal
是1、2或3(- CCD_ 13表示完整的帧数据
由于保留了所有原始帧,只添加了一个帧,因此删除EXPAND_FRAMES
选项以保持所有帧的压缩更有效。