9

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

risposta

7

Se si utilizza Eclipse, è necessario installare Bytecode Outline - è indispensabile.

ho costruito un piccolo test per quello che si vuole ottenere (questo dovrebbe corrispondere alla firma del metodo di prova, sarà necessario modificare il pacchetto e nome di classe):

package checkASM; 

public class MethodCall { 

    public boolean Test(String a, boolean b, String c) { 
     System.out.println("GOTit"); 
     return false; 
    } 
} 

richiede i seguenti bytecode per costruire il metodo:

{ 
mv = cw.visitMethod(ACC_PUBLIC, "Test", 
    "(Ljava/lang/String;ZLjava/lang/String;)Z", null, null); 
mv.visitCode(); 
Label l1 = new Label(); 
mv.visitLabel(l1); 
mv.visitFieldInsn(GETSTATIC, "java/lang/System", 
    "out", "Ljava/io/PrintStream;"); 
mv.visitLdcInsn("GOTit"); 
mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", 
    "println", "(Ljava/lang/String;)V"); 
Label l2 = new Label(); 
mv.visitLabel(l2); 
mv.visitInsn(ICONST_0); 
mv.visitInsn(IRETURN); 
Label l3 = new Label(); 
mv.visitLabel(l3); 
mv.visitLocalVariable("this", "LcheckASM/MethodCall;", null, l1, l3, 0); 
mv.visitLocalVariable("a", "Ljava/lang/String;", null, l1, l3, 1); 
mv.visitLocalVariable("b", "Z", null, l1, l3, 2); 
mv.visitLocalVariable("c", "Ljava/lang/String;", null, l1, l3, 3); 
mv.visitMaxs(4, 4); 
mv.visitEnd(); 
} 

Le chiamate a visitLineNumber possono essere omesse. A quanto pare, ti mancano tutte le etichette, hai dimenticato di caricare i parametri del metodo, non hai ignorato il valore restituito, hai impostato i valori errati per visitMaxs (questo non è necessariamente necessario, dipende dai tuoi flag ClassWriter se ricordo correttamente) e non visita le variabili locali (o i parametri in questo caso).

Inoltre, il tuo caricamento di classe sembra essere un po 'confuso/incasinato. Non ho il vaso (quindi non posso dire se questi lavori), ma forse si potrebbe sostituire principale e Loader:

principale:

import java.io.File; 
import java.io.FileNotFoundException; 
import java.net.URL; 

public class Main { 
    public static void main(String[] args) { 
     try { 
      Loader.instrumentTmcore(args); 
     } catch (Exception e) { 
      System.err.println("Ooops"); 
      e.printStackTrace(); 
     } 
    } 
} 

Loader:

import java.io.IOException; 
import java.io.PrintWriter; 
import java.lang.reflect.Constructor; 
import java.lang.reflect.Method; 
import java.net.URL; 
import java.net.URLClassLoader; 

import org.objectweb.asm.ClassReader; 
import org.objectweb.asm.ClassWriter; 
import org.objectweb.asm.MethodVisitor; 

public class Loader { 

    public static ClassReader fetchReader(String binaryName) throws Exception { 
     return new ClassReader(
       Loader.class.getClassLoader().getSystemResourceAsStream(
        binaryName.replace('.', '/') + ".class" 
       ) 
      ) 
     ; 
    } 

    public static synchronized Class<?> loadClass(byte[] bytecode) 
       throws Exception { 
     ClassLoader scl = ClassLoader.getSystemClassLoader(); 
     Class<?>[] types = new Class<?>[] { 
       String.class, byte[].class, int.class, int.class 
     }; 
     Object[] args = new Object[] { 
       null, bytecode, 0, bytecode.length 
     }; 
     Method m = ClassLoader.class.getMethod("defineClass", types); 
     m.setAccessible(true); 
     return (Class<?>) m.invoke(scl, args); 
    } 

    public static void instrumentTmcore(String[] args) throws Exception { 
     ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES); 
     MethodReplacer mr = new MethodReplacer(cw, "Test", 
        "(Ljava/lang/String;ZLjava/lang/String;)Z"); 
     fetchReader("tmcore.objwin").accept(mr, ClassReader.EXPAND_FRAMES); 
     loadClass(cw.toByteArray()); 
     Class.forName("tmcore.game") 
      .getMethod("main", new Class<?>[] {args.getClass()}) 
      .invoke(null, new Object[] { args }); 
    } 
} 
+0

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

+0

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

+0

Inoltre, poiché il mio ClassWriter è stato creato con COMPUTE_FRAMES, il valore di visitMaxx non ha importanza, corretto? – emist

0

RISPOSTA ASKER'S spostato da DOMANDA

Il bytecode java non è mai stato un problema. È il modo in cui stavo caricando il barattolo che rendeva impossibile strumentare il codice.

Grazie a Ame per avermi aiutato affrontarlo.

Il seguente codice funziona:

PRINCIPALE

import java.io.File; 
import java.io.FileNotFoundException; 
import java.io.IOException; 
import java.io.PrintWriter; 
import java.lang.reflect.Constructor; 
import java.lang.reflect.Method; 
import java.io.FileInputStream; 

import org.objectweb.asm.ClassReader; 
import org.objectweb.asm.ClassWriter; 
import org.objectweb.asm.Opcodes; 

public class Main implements Opcodes 
{ 
    public static void main(String[] args) throws Exception 
    { 

     byte[] obj = readClass("tmcore/obj.class"); 
     ClassReader objReader = new ClassReader(obj); 
     ClassWriter objWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS); 

     MethodReplacer demoReplacer = new MethodReplacer(objWriter, "run", "()V"); 
     demoReplacer.visit(V1_6, ACC_PUBLIC + ACC_SUPER, "tmcore/obj", null, "java/applet/Applet", new String[] { "java/lang/Runnable" }); 
     objReader.accept(demoReplacer, ClassReader.EXPAND_FRAMES); 

     objReader = new ClassReader(objWriter.toByteArray()); 

     Class objC = Loader.loadClass(objWriter.toByteArray(), "tmcore.obj"); 
     if(objC == null) 
     { 
      System.out.println("obj cannot be loaded"); 
     } 

     Class game = ClassLoader.getSystemClassLoader().loadClass("tmcore.game"); 
     if(game == null) 
     { 
      System.out.println("Can't load game"); 
      return; 
     } 

     Constructor ctor = game.getDeclaredConstructor(String[].class); 
     if(ctor == null) 
     { 
      System.out.println("can't find constructor"); 
      return; 
     } 

     //Instantiate the class by calling the constructor 
     String[] arg = {"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 = game.getDeclaredMethod("main", String[].class); 
     //call the main method 
     method.invoke(instance); 

    } 


    public static void verifyValidPath(String path) throws FileNotFoundException 
    { 
      File filePath = new File(path); 
      if (!filePath.exists()) 
      { 
       throw new FileNotFoundException(filePath.getPath()); 
      } 
    } 

    public static byte[] readClass(String classpath) throws Exception 
    { 
     verifyValidPath(classpath); 
     File f = new File(classpath); 

     FileInputStream file = new FileInputStream(f); 
     if(file == null) 
      throw new FileNotFoundException(); 

     byte[] classbyte = new byte[(int)f.length()]; 

     int offset = 0, numRead = 0; 
     while (offset < classbyte.length 
       && (numRead=file.read(classbyte, offset, classbyte.length-offset)) >= 0) 
     { 
      offset += numRead; 
     } 

     if (offset < classbyte.length) 
     { 
      file.close(); 
      throw new IOException("Could not completely read file "); 
     } 

     file.close(); 
     return classbyte; 
    } 
} 

CARICATORE:

import java.io.IOException; 
import java.lang.reflect.Method; 
import java.net.URL; 
import java.net.URLClassLoader; 

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"); 
      } 

    } 

    public static 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; 
    } 
} 

MethodReplacer rimane lo stesso.