Reputation: 426
I need to use ASM to find a local variable inside a method, which is:
String var4 = "hello!";
I have created three classes. One that does the transformation, one that extends ClassVisitor, and one that extends MethodVisitor, like so:
Transformer entry point (Transformationer.java)
package RainbowBansTransAgent;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;
import org.objectweb.asm.*;
public class Transformationer implements ClassFileTransformer {
public byte[] transform(String arg1, byte[] arg2){
ClassReader cr = new ClassReader(arg2);
ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_FRAMES);
cr.accept(cw, 0);
return cw.toByteArray();
}
@Override
public byte[] transform(ClassLoader arg0, String className, Class<?> arg2,
ProtectionDomain arg3, byte[] arg4)
throws IllegalClassFormatException {
BooleanKeys.transformer_loaded = true;
byte[] b = null;
String realName = className.replaceAll("/", ".");
if(realName.equals("joebkt.PlayerList")){
if(BooleanKeys.returned_bytes){
return null;
}else{
BooleanKeys.found_class = true;
b = transform(realName, arg4);
if(b !=null){
BooleanKeys.returned_bytes = true;
}
}
}
else System.out.println("Class name " + realName + " is not what we're looking for!");
return b;
}
}
ClassVisior Extender (RBClassVisitor.java)
package RainbowBansTransAgent;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
public class RBClassVisitor extends ClassVisitor{
public RBClassVisitor() {
super(Opcodes.ASM5);
}
@Override
public MethodVisitor visitMethod(int access, String name, String desc,
String signature, String[] exceptions) {
MethodVisitor mv = super.visitMethod(access, name, desc, signature,
exceptions);
return new RBMethodVisitor(mv);
}
}
MethodVisitor Extender (RBMethodVisitor.java)
package RainbowBansTransAgent;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
public class RBMethodVisitor extends MethodVisitor {
MethodVisitor mv;
public RBMethodVisitor(MethodVisitor mv) {
super(Opcodes.ASM5, mv);
this.mv = mv;
}
public void visitLineNumber(int line, Label start){
if(line == 409){
mv.visitCode();
}
}
public void visitCode(){
}
}
As you can see, my visitCode() method is empty. I understand this is the method where the bytecode manipulation is supposed to happen.
I saw that MethodVisitor has a
mv.visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index);
method, but I have no idea how to use the Label class correctly.
My transformer will read a file, and change the variable to the contents of the file. Using ASM, how do I do that?
EDIT: My bytecode for the thing I want to change says: ldc "hello!" (java.lang.String)
and I want to change it to:
ldc "goodbye!" (java.lang.String)
Upvotes: 3
Views: 2377
Reputation: 298233
You shouldn’t try to do bytecode transformations without a fundamental understanding of how the Java bytecode works. The primary source for information about it is The Java® Virtual Machine Specification. For a starter, you may read §3 “Compiling for the Java Virtual Machine” to learn, how certain language constructs map to byte code instructions.
The most important thing, you have to understand for your intended kind of transformation, is, that there are no local variables (the way you know it from the Java language) on the byte code level. There is room for a certain number of local variables in a stack frame, which is addressed by a numerical index. The method visitLocalVariable
, you have discovered in the ASM API will only be called, if the class file has been compiled with debugging information included.
If it gets called, it will tell you about a local variable name
and to which index
it maps. The Label
arguments tell you about the scope of the variable as outside of its scope, the index might be used for a different variable. If you write code using this way of learning about the particular index of a variable, you have to keep in mind, that this code will only work with a class containing debugging hints.
So on the byte code level, there is no formal declaration of a variable var4
, but only an assignment of the string "hello"
to a particular variable which implicitly creates that variable if it didn’t exist. The assignment is implemented on the byte code level as two instructions, ldc "hello"
, followed by astore n
, where n
is the index of the local variable. Actually, ldc
only bears an index into the constant pool holding the string, but ASM takes care of this and will invoke visitLdcInsn("hello")
when encountering this instruction.
So you may search for the two instruction sequence, which implies that you have to find out the right index first (e.g. using the debugging information, if present). Or, if the string "hello"
is expected to appear only at this single assignment, waiting for an occurrence of visitLdcInsn("hello")
and replacing it by a different constant string is the simplest form of replacement.
Upvotes: 4