在检测类时,插入堆栈帧的偏移量与现有堆栈帧重叠



我所做的是在运行时插入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_FRAMESF_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选项以保持所有帧的压缩更有效。

相关内容

  • 没有找到相关文章

最新更新