Java ASM Байт-код Модификация-Изменение тела метода

У меня есть метод класса в банке, тело которого я хочу обменять на свое. В этом случае я просто хочу, чтобы метод распечатал "GOT IT" на консоли и вернул true;

Я использую системный загрузчик для загрузки классов банку. Я использую отражение, чтобы системный загрузчик классов мог загружать классы с помощью байт-кода. Эта часть, кажется, работает правильно.

Я следую примеру замены метода, найденному здесь: asm.ow2.org/current/asm-transformations.pdf.

Мой код выглядит следующим образом:

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

Проблема, кажется, в mv.visitMethodInsn(access, cname, newName, desc); в generateMethodBody внутри MethodReplacer,

Я получаю ошибку "Недопустимый тип в постоянном пуле".

Я не уверен, что мне не хватает... но после прочтения и тестирования в течение примерно 3 дней я все еще никуда не доберусь.

[Редактировать]

Если вам интересно, tmcore однопользовательская игра "Возражение" для юристов. Я делаю это ради удовольствия. Программа успешно запускает игру и все нормально, удаляя модификации из MethodReplacer заставляет игру вести себя как задумано. Так что проблема, кажется, изолирована от плохого байт-кода / модификаций мной внутри метода заменителя.

[EDIT2]

CheckClassAdapter.verify(cr, true, pw); возвращает тот же байт-код, который должна иметь функция до редактирования. Это как если бы изменения не делались.

[EDIT3]

копия classtoload закомментировано согласно комментариям

2 ответа

Решение

Если вы используете Eclipse, вы должны установить Bytecode Outline - это необходимо.

Я создал небольшой тест для того, чего вы хотите достичь (это должно соответствовать сигнатуре вашего метода тестирования, вам придется изменить пакет и имя класса):

package checkASM;

public class MethodCall {

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

для построения метода требуется следующий байт-код:

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

Звонки в visitLineNumber может быть опущен Итак, по-видимому, вы пропустили все метки, забыли загрузить параметры метода, не проигнорировали возвращаемое значение, установили неправильные значения для visitMaxs (это не обязательно необходимо, это зависит от ваших флагов ClassWriter, если я правильно помню) и не посещал локальные переменные (или параметры в этом случае).

Кроме того, ваша загрузка классов выглядит немного запутанной / запутанной. У меня нет банки (поэтому я не могу сказать, работают ли они), но, возможно, вы могли бы заменить Main и Loader:

Главный:

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

погрузчик:

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

АСКЕР ОТВЕТИЛ НА ВОПРОС

Java-байт-код никогда не был проблемой. Это был способ, которым я загружал флягу, которая сделала невозможным инструмент код.

Спасибо Эме за помощь в решении этой проблемы.

Следующий код работает:

ГЛАВНЫЙ

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

ПОГРУЗЧИК:

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 остается прежним.

Другие вопросы по тегам