2012-08-02 12 views
9

Mam metodę klasy w słoiku, którego ciało chcę wymienić na własne. W tym przypadku po prostu chcę, aby metoda wydrukowała "GOT IT" na konsolę i zwróciła true;Java ASM Bytecode Modyfikacja - Zmienianie ciał metodowych

Używam programu ładującego system do ładowania klas słoika. Używam refleksji, aby system ładujący klasy mógł ładować klasy według kodu bajtowego. Ta część wydaje się działać poprawnie.

Podążam za przykładem zastąpienia metody znajdującym się tutaj: asm.ow2.org/current/asm-transformations.pdf.

Mój kod wygląda następująco:

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

Problem wydaje się być w mv.visitMethodInsn(access, cname, newName, desc); w generateMethodBody wewnątrz MethodReplacer.

Otrzymuję błąd "Nielegalny typ w stałej puli".

Nie jestem pewien, czego mi brakuje ... ale po przeczytaniu i przetestowaniu przez około 3 dni wciąż nie dostaję się nigdzie.

[Edytuj]

W przypadku, gdy zastanawiasz się, tmcore to pojedynczy gracz "Sprzeciw" gra dla prawników. Robię to dla zabawy. Program pomyślnie uruchamia grę i wszystko jest w porządku, usunięcie modyfikacji z MethodReplacer powoduje, że gra zachowuje się zgodnie z przeznaczeniem. Tak więc problem wydaje się być odizolowany od złego kodu bajtowego/modyfikacji przeze mnie w zamienniku metody.

[EDIT2]

CheckClassAdapter.verify(cr, true, pw); zwraca dokładną tego samego kodu bajtowego, że funkcja ma mieć przed edycją. To tak, jakby zmiany nie były dokonywane.

[Edit3]

kopia classtoload wykomentowane jak za komentarze

Odpowiedz

7

Jeśli używasz Eclipse, należy zainstalować Bytecode Outline - jest to niezbędne.

Zbudowałem mały test dla tego, co chcesz osiągnąć (powinno to pasuje podpis swojej metodzie badania, trzeba będzie zmienić pakiet i classname):

package checkASM; 

public class MethodCall { 

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

wymaga następującego kodu bajtowego zbudować metoda:

{ 
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(); 
} 

Połączenia z numerem visitLineNumber można pominąć. Najwyraźniej brakuje Ci wszystkich etykiet, zapomniałeś załadować parametry metody, nie zignorowałeś zwracanej wartości, ustawiłeś złe wartości dla visitMaxs (nie jest to konieczne, zależy to od flag ClassWriter, jeśli dobrze pamiętam) i nie odwiedzaj zmienne lokalne (lub parametry w tym przypadku).

Co więcej, ładowanie klas wydaje się być nieco pomieszane/pomieszane. nie mam słoik (więc nie mogę powiedzieć, czy tych prac), ale może można wymienić główne i ładowarki:

główne:

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

Ładowarka:

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

Witam Ame, Mam zainstalowaną wtyczkę. Masz dobre pojęcie o tym, co próbuję zrobić poza tym, że zamiast: public boolean Test (String a, boolean b, String c) { Test (a, b, c); System.out.println ("GOTit"); return false; } } Poszukuję tylko: public boolean Test (String a, boolean b, String c) { System.out.println ("GOTit"); return false; } Kod bajtowy generowany przez wtyczkę oraz ASMifer pasuje do kodu bajtowego w mojej metodzie generateBody. – emist

+0

Jeśli pominę: mv.visitVarInsn (Opcodes.ALOAD, 0); mv.visitMethodInsn (dostęp, cname, newName, opis); Nie otrzymuję błędu, ale metoda działa również z oryginalnym treścią. – emist

+0

Ponadto, ponieważ mój ClassWriter jest zbudowany z COMPUTE_FRAMES, wartość visitMaxx nie ma znaczenia, prawda? – emist

0

Asker'S ODPOWIEDŹ MOVED od pytania

Kod bajtowy java nigdy nie był problemem. To jest sposób, w jaki ładowałem słoik, co uniemożliwiło zaprogramowanie kodu.

Podziękowania dla Ame za pomoc w rozwiązaniu problemu.

Poniższy kod działa:

MAIN

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

ładowarka:

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 pozostaje taka sama.