Ho un metodo di una classe in un jar il cui corpo voglio scambiare con il mio. In questo caso voglio solo che il metodo stampi "GOT IT" sulla console e restituisca true;Modifica ASM bytecode Java - Modifica corpi metodo
Sto utilizzando il caricatore di sistema per caricare le classi del jar. Sto usando reflection per fare in modo che il classloader del sistema sia in grado di caricare le classi in bytecode. Questa parte sembra funzionare correttamente.
Sto seguendo l'esempio di sostituzione del metodo trovato qui: asm.ow2.org/current/asm-transformations.pdf.
Il mio codice è il seguente:
public class Main
{
public static void main(String[] args)
{
URL[] url = new URL[1];
try
{
url[0] = new URL("file:////C://Users//emist//workspace//tmloader//bin//runtime//tmgames.jar");
verifyValidPath(url[0]);
}
catch (Exception ex)
{
System.out.println("URL error");
}
Loader l = new Loader();
l.loadobjection(url);
}
public static void verifyValidPath(URL url) throws FileNotFoundException
{
File filePath = new File(url.getFile());
if (!filePath.exists())
{
throw new FileNotFoundException(filePath.getPath());
}
}
}
class Loader
{
private static final Class[] parameters = new Class[] {URL.class};
public static void addURL(URL u) throws IOException
{
URLClassLoader sysloader = (URLClassLoader) ClassLoader.getSystemClassLoader();
Class sysclass = URLClassLoader.class;
try
{
Method method = sysclass.getDeclaredMethod("addURL", parameters);
method.setAccessible(true);
method.invoke(sysloader, new Object[] {u});
}
catch (Throwable t)
{
t.printStackTrace();
throw new IOException("Error, could not add URL to system classloader");
}
}
private Class loadClass(byte[] b, String name)
{
//override classDefine (as it is protected) and define the class.
Class clazz = null;
try
{
ClassLoader loader = ClassLoader.getSystemClassLoader();
Class cls = Class.forName("java.lang.ClassLoader");
java.lang.reflect.Method method =
cls.getDeclaredMethod("defineClass", new Class[] { String.class, byte[].class, int.class, int.class });
// protected method invocaton
method.setAccessible(true);
try
{
Object[] args = new Object[] {name, b, new Integer(0), new Integer(b.length)};
clazz = (Class) method.invoke(loader, args);
}
finally
{
method.setAccessible(false);
}
}
catch (Exception e)
{
e.printStackTrace();
System.exit(1);
}
return clazz;
}
public void loadobjection(URL[] myJar)
{
try
{
Loader.addURL(myJar[0]);
//tmcore.game is the class that holds the main method in the jar
/*
Class<?> classToLoad = Class.forName("tmcore.game", true, this.getClass().getClassLoader());
if(classToLoad == null)
{
System.out.println("No tmcore.game");
return;
}
*/
MethodReplacer mr = null;
ClassReader cr = new ClassReader("tmcore.objwin");
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
MethodVisitor mv = null;
try
{
mr = new MethodReplacer(cw, "Test", "(Ljava/lang/String;ZLjava/lang/String;)Z");
}
catch (Exception e)
{
System.out.println("Method Replacer Exception");
}
cr.accept(mr, ClassReader.EXPAND_FRAMES);
PrintWriter pw = new PrintWriter(System.out);
loadClass(cw.toByteArray(), "tmcore.objwin");
Class<?> classToLoad = Class.forName("tmcore.game", true, this.getClass().getClassLoader());
if(classToLoad == null)
{
System.out.println("No tmcore.game");
return;
}
//game doesn't have a default constructor, so we need to get the reference to public game(String[] args)
Constructor ctor = classToLoad.getDeclaredConstructor(String[].class);
if(ctor == null)
{
System.out.println("can't find constructor");
return;
}
//Instantiate the class by calling the constructor
String[] args = {"tmgames.jar"};
Object instance = ctor.newInstance(new Object[]{args});
if(instance == null)
{
System.out.println("Can't instantiate constructor");
}
//get reference to main(String[] args)
Method method = classToLoad.getDeclaredMethod("main", String[].class);
//call the main method
method.invoke(instance);
}
catch (Exception ex)
{
System.out.println(ex.getMessage());
ex.printStackTrace();
}
}
}
public class MethodReplacer extends ClassVisitor implements Opcodes
{
private String mname;
private String mdesc;
private String cname;
public MethodReplacer(ClassVisitor cv, String mname, String mdesc)
{
super(Opcodes.ASM4, cv);
this.mname = mname;
this.mdesc = mdesc;
}
public void visit(int version, int access, String name, String signature,
String superName, String[] interfaces)
{
this.cname = name;
cv.visit(version, access, name, signature, superName, interfaces);
}
public MethodVisitor visitMethod(int access, String name, String desc, String signature,
String[] exceptions)
{
String newName = name;
if(name.equals(mname) && desc.equals(mdesc))
{
newName = "orig$" + name;
generateNewBody(access, desc, signature, exceptions, name, newName);
System.out.println("Replacing");
}
return super.visitMethod(access, newName, desc, signature, exceptions);
}
private void generateNewBody(int access, String desc, String signature, String[] exceptions,
String name, String newName)
{
MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions);
mv.visitCode();
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitMethodInsn(access, cname, newName, desc);
mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
mv.visitLdcInsn("GOTit!");
mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V");
mv.visitInsn(ICONST_0);
mv.visitInsn(IRETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
}
}
Il problema sembra essere al mv.visitMethodInsn(access, cname, newName, desc);
in generateMethodBody
all'interno MethodReplacer
.
Viene visualizzato un errore "Tipo non valido in pool costante".
Non sono sicuro di cosa mi manchi ... ma dopo aver letto e provato per circa 3 giorni non riesco ancora a raggiungere nessun luogo.
[Edit]
Nel caso ve lo stiate chiedendo, tmcore
è un gioco single player "obiezione" per gli avvocati. Lo sto facendo per il gusto di farlo. Il programma avvia correttamente il gioco e tutto è a posto, rimuovendo le modifiche da MethodReplacer
il gioco si comporta come progettato. Quindi il problema sembra essere isolato da codice byte/modifiche non valido da parte mia all'interno del sostituto del metodo.
[EDIT2]
CheckClassAdapter.verify(cr, true, pw);
restituisce esattamente lo stesso bytecode che si suppone la funzione di avere prima del montaggio. È come se le modifiche non fossero state fatte.
[Edit3]
copia di classtoload
commentato out come da commenti
Hi Ame, mi hai il plugin installato. Lei ha l'idea giusta su quello che sto cercando di fare, tranne che invece di: test boolean pubblico (String a, b booleano, String c) { test (a, b, c); System.out.println ("GOTit"); return false; } Sto cercando di fare solo: test booleano pubblico (stringa a, booleano b, stringa c) { System.out.println ("GOTit"); return false; } Il bytecode che il plugin genera così come ASMifer corrisponde al codice byte dentro il mio metodo generateBody. – emist
Se ometto: mv.visitVarInsn (Opcodes.ALOAD, 0); mv.visitMethodInsn (access, cname, newName, desc); Non ottengo alcun errore ma il metodo viene eseguito anche con il suo corpo originale. – emist
Inoltre, poiché il mio ClassWriter è stato creato con COMPUTE_FRAMES, il valore di visitMaxx non ha importanza, corretto? – emist