JVM类加载以及执行的实战(3)

public static int bytes2Int(byte[] b, int start, int len) {
        int sum = 0;
        int end = start + len;
        for (int i = start; i < end; i++) {
            int n = ((int) b[i]) & 0xff;
            n <<= (--len) * 8;
            sum = n + sum;
        }
        return sum;
    }

public static byte[] int2Bytes(int value, int len) {
        byte[] b = new byte[len];
        for (int i = 0; i < len; i++) {
            b[len - i - 1] = (byte) ((value >> 8 * i) & 0xff);
        }
        return b;
    }

public static String bytes2String(byte[] b, int start, int len) {
        return new String(b, start, len);
    }

public static byte[] string2Bytes(String str) {
        return str.getBytes();
    }

public static byte[] bytesReplace(byte[] originalBytes, int offset, int len, byte[] replaceBytes) {
        byte[] newBytes = new byte[originalBytes.length + (replaceBytes.length - len)];
        System.arraycopy(originalBytes, 0, newBytes, 0, offset);
        System.arraycopy(replaceBytes, 0, newBytes, offset, replaceBytes.length);
        System.arraycopy(originalBytes, offset + len, newBytes, offset + replaceBytes.length, originalBytes.length - offset - len);
        return newBytes;
    }
}

最后,来看看实现替换符号引用以及得到输出日志的类

/**
 * JavaClass执行工具
 *
 * @author zzm
 */
public class JavaClassExecuter {

/**
    * 执行外部传过来的代表一个Java类的Byte数组<br>
    * 将输入类的byte数组中代表java.lang.System的CONSTANT_Utf8_info常量修改为劫持后的HackSystem类
    * 执行方法为该类的static main(String[] args)方法,输出结果为该类向System.out/err输出的信息
    * @param classByte 代表一个Java类的Byte数组
    * @return 执行结果
    */
    public static String execute(byte[] classByte) {
        HackSystem.clearBuffer();
        ClassModifier cm = new ClassModifier(classByte);
        byte[] modiBytes = cm.modifyUTF8Constant("java/lang/System", "org/fenixsoft/classloading/execute/HackSystem");
        HotSwapClassLoader loader = new HotSwapClassLoader();
        Class clazz = loader.loadByte(modiBytes);
        try {
            Method method = clazz.getMethod("main", new Class[] { String[].class });
            method.invoke(null, new String[] { null });
        } catch (Throwable e) {
            e.printStackTrace(HackSystem.out);
        }
        return HackSystem.getBufferString();
    }
}

传进来待执行类的class文件的字节数组,先将符号替换,然后加载该类,反射调用该类的main方法,最后将HackSystem类收集到的输出日志返回。

为了更直观的看到运行的结果,可以写一个jsp文件,通过浏览器去访问。

<%@ page import="java.lang.*" %>
<%@ page import="java.io.*" %>
<%@ page import="org.fenixsoft.classloading.execute.*" %>
<%
    InputStream is = new FileInputStream("c:/TestClass.class");
    byte[] b = new byte[is.available()];
    is.read(b);
    is.close();

out.println("<textarea>");
    out.println(JavaClassExecuter.execute(b));
    out.println("</textarea>");
%>

这里将待执行类TestClass.class放到服务器的C盘。只要TestClass里面main方法,有调用System.out,就可以将输出内容展现到页面上。我自己在Tomcat上面的项目里也测试了一把,现在把代码也贴出来

public class TestClass {
    public static void main(String[] args) {
        System.out.println("hello world!!!");
        ClassLoader cl = TestClass.class.getClassLoader();
        System.out.println("self: " + cl);
        while (cl.getParent() != null) {
            System.out.println(cl.getParent().getClass());
            cl = cl.getParent();
        }
    }
}

大家可以那我这个类去试一试,而且还可以根据输出结果去温习一下Tomcat的类加载体系。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/8ff2bf33cd541bc70e3386cb68fc3482.html